2 条题解

  • 0
    @ 2016-11-08 15:53:30

    主要的算法下面两位已经讲得很清楚了 补充一点
    就是题目中边权可能为0,如果出现这样的情况会导致算法出错
    处理方法是读入的时候先用并查集缩点就好了

    (然而我**并没有**写这个判断,还是AC了,我猜这个大概是出题人笔误?
    ```c++
    #include <cstdio>
    #include <algorithm>
    #define N 100050
    #define LG 18
    #define swap(a,b) std::swap(a,b)
    typedef long long ll;
    inline int RD()
    {
    char cr;int res;
    while( (cr=getchar())<'0' || cr>'9');
    res=cr-'0';
    while( (cr=getchar())>='0' && cr<='9')
    res=res*10+cr-'0';
    return res;
    }
    inline void PD(ll x)
    {
    if(!x) return;
    PD(x/10);
    putc('0'+x%10,stdout);
    }
    inline void PR(ll x,ll y)
    {
    x? PD(x): (void)putc('0',stdout);
    putc(' ',stdout);
    y? PD(y): (void)putc('0',stdout);
    putc('\n',stdout);
    }
    int eg[N<<1],nxt[N<<1],len[N<<1],head[N],egtot;
    int fa[N][LG];
    ll dis[N][LG];
    int h[N];
    ll cnt[N],disto[N];
    int n;
    inline void addEdge(int p1,int p2,int wei)
    {
    eg[++egtot]=p2;
    nxt[egtot]=head[p1];
    len[egtot]=wei;
    head[p1]=egtot;
    }
    void dfs(int p)
    {
    for(int i=1;i<=LG-1;i++)
    {
    fa[p][i]=fa[fa[p][i-1]][i-1];
    dis[p][i]=dis[p][i-1]+dis[fa[p][i-1]][i-1];
    }
    for(int i=head[p],pt;i;i=nxt[i])
    if((pt=eg[i])!=fa[p][0])
    {
    fa[pt][0]=p;
    dis[pt][0]=len[i];
    h[pt]=h[p]+1;
    disto[pt]=disto[p]+len[i];
    dfs(pt);
    cnt[p]+=cnt[pt];
    }
    }
    int xlift(int x,int d)
    {
    for(int i=LG-1;i>=0;i--)
    if((1<<i)<=d)
    {
    d-=1<<i;
    x=fa[x][i];
    }
    return x;
    }
    int lenlift(int x,ll sumd)
    {
    for(int i=LG-1;i>=0;i--)
    if(sumd>=dis[x][i])
    {
    sumd-=dis[x][i];
    x=fa[x][i];
    }
    return x;
    }
    int getlca(int x,int y)
    {
    bool trans=0;
    if(h[x]<h[y]) swap(x,y),trans=1;
    x=xlift(x,h[x]-h[y]);
    if(x==y) return x;
    for(int i=LG-1;i>=0;i--)
    if(fa[x][i]!=fa[y][i])
    x=fa[x][i],y=fa[y][i];
    return fa[x][0];
    }

    void solve(int x,int y)
    {
    if(x==y){PR(0,0);return;}
    int lca=getlca(x,y);
    ll dx=disto[x]-disto[lca];
    ll dy=disto[y]-disto[lca];

    if(dx==dy)
    {
    int xson=xlift(x,h[x]-h[lca]-1);
    int yson=xlift(y,h[y]-h[lca]-1);
    PR(cnt[xson],cnt[yson]);
    return;
    }
    bool trans=0;
    ll xans,yans;
    if(dx<dy) swap(dx,dy),swap(x,y),trans=1;
    int mid=lenlift(x,(dx+dy)/2);
    if(disto[x]-disto[mid]==(dx+dy)/2)
    {
    int xson=xlift(x,h[x]-h[mid]-1);
    xans=cnt[xson];
    }
    else
    xans=cnt[mid];
    yans=cnt[1]-cnt[mid];
    if(trans) swap(xans,yans);
    PR(xans,yans);
    }
    int main()
    {
    n=RD();
    for(int i=1,p1,p2,wei;i<=n-1;i++)
    {
    p1=RD();p2=RD();wei=RD()*2;//the prog will get WA if wei==0
    addEdge(p1,p2,wei);
    addEdge(p2,p1,wei);
    }
    for(int i=1;i<=n;i++)
    cnt[i]=RD();
    fa[1][0]=1;h[1]=1;
    dfs(1);
    int Ques=RD();
    while(Ques--)
    {
    int x,y;x=RD();y=RD();
    solve(x,y);
    }
    }

  • 0
    @ 2015-08-10 16:44:38

    树上倍增写了好久……题解写了好久……
    不是不想在这个地方粘,我用word统计了一下题解字数,已经上千字了……
    这里看详细题解

    • @ 2015-08-10 17:35:19

      好吧刚刚看到了版规。

      先想一个可行的算法。通过分析题目可以发现,这棵树可以以任意一个结点为根,就默认为1吧。对于每一次询问x和y,都有一条唯一确定的路径x→y,这条路径把树分为三部分——离x更近的路径外的点、离y更近的路径外的点和路径上的点。

      显然,离x更近的路径外的点上所有咖啡厅都离x更近,离y更近的路径外的点上所有咖啡厅都离y更近。下面分析路径上的点。因为我们人为确定了树根为1,所以x可能是y的祖先,y也可能是x的祖先,x与y也有可能互不为祖先,那路径x→y的形态就很多了,不容易处理。

      我们记t=lca(x,y),即t为x与y的最近公共祖先,并用这个t把路径分为两部分——x→t和y→t,这样这两部分都是从叶子指向根的路径,处理起来就比较方便。从叶子到根,我们可以算出这条路径上到x与y距离最接近的一对点(或者算出了某个到x与y的距离相同的点),至少可以暴力枚举来算。暂且称它为“**转折点**”吧。

      我们记w(i,j)为从i到j的距离,那么有w(j,i)==w(i,j)。(貌似是废话)所谓**转折点**,要么是t,要么在x→t上,要么在y→t上,不可能跨越t(如果真有跨越t的**转折点**,那t一定是**转折点**)。下面分情况讨论。

      先不考虑t==x或t==y的情况。如果t是转折点,那么w(x,t)==w(y,t)。记t的某一儿子结点是tmpx,且tmpx是x的祖先;同理记一个tmpy,那么t以及t的祖先以及t除了tmpx和tmpy的所有其他儿子中所含的咖啡厅对答案没有贡献,因为这些咖啡厅距离x和y相等。离x更近的所有咖啡厅都在以tmpx为根的子树中,离y更近的所有咖啡厅都在以tmpy为根的子树中,想办法搞出来总数即可。这个可以参照样例的第一个输出。

      那如果t不是转折点又如何呢?以转折点在x→t上为例,从x往t爬,利用w()函数(先别问w()怎么求),总能确定转折点。y→t上同理。离x近的咖啡厅总数是从离x近的转折点往x算的,离y近的咖啡厅总数是从离y近的转折点往y算的。这样就知道答案怎么求了。

      把t==x或t==y的情况代入上述分析,发现答案的求法基本相同,只是tmpx和tmpy不是转折点的儿子,而是从转折点往x或y上“多走”一步的结点。

      我们记dist(x)为从x到根的距离,dep(x)为x结点的深度,s(x)为以1为根后的整棵树中以x为根的子树含有咖啡厅的总数,它们都能在同一遍从根开始的dfs中用O(n)的时间里确定。那么w(i,j)=dist(i)+dist(j)−2∗dist(lca(i,j)),可以在O(1)的时间里确定。

      现在谈一下乱搞的基础:这个题没有涉及到修改操作!
      “引理”:树上乱搞用倍增。
      lca倍增求出,O(nlogn);
      所有“往上爬”的操作,用倍增,O(logn);
      再暴力一点,把所有“多走一步”变成从孙子辈“找祖先”,继续倍增,O(logn);
      别看题目看起来挺复杂的,其实是很多倍增的小题组合起来了,一部分一部分耐心写下去就可以了。

      看数据范围是要开longlong的,输出时纠结了一会儿”%lld”和”%I64d”,于是直接iostream了。
      时限3s,总时间复杂度O((n+q)logn),最慢的2s多一点,开读写优化可能能杀进1s。

      #include <cstdio>
      #include <algorithm>
      #include <cstring>
      #include <iostream>
      #define ll long long
      #define maxn 100005
      #define root 1
      #define nil 0
      using namespace std;
      int n, q, fa[maxn][20], dep[maxn];
      ll d[maxn], val[maxn], s[maxn];
      ll u[maxn << 1], v[maxn << 1], w[maxn << 1], nxt[maxn << 1], pnt[maxn], e;
      bool vis[maxn];
      void add(ll a, ll b, ll c)
      {
      u[++e] = a; v[e] = b; w[e] = c;
      nxt[e] = pnt[a]; pnt[a] = e;
      u[++e] = b; v[e] = a; w[e] = c;
      nxt[e] = pnt[b]; pnt[b] = e;
      }
      void dfs(int rot)
      {
      vis[rot] = true;
      s[rot] = val[rot];
      for(int j = pnt[rot]; j != nil; j = nxt[j])
      {
      if(!vis[v[j]])
      {
      fa[v[j]][0] = rot;
      dep[v[j]] = dep[rot] + 1;
      d[v[j]] = d[rot] + w[j];
      dfs(v[j]);
      s[rot] += s[v[j]];
      }
      }
      }
      void init()
      {
      ll a, b, c;
      cin >> n;
      for(int i = 1; i < n; ++i)
      {
      cin >> a >> b >> c;
      add(a, b, c);
      }
      for(int i = 1; i <= n; ++i)
      {
      cin >> val[i];
      }
      memset(vis, 0, sizeof(vis));
      dep[root] = 1;
      dfs(root);
      for(int j = 1; j < 20; ++j) for(int i = 1; i <= n; ++i)
      {
      fa[i][j] = fa[fa[i][j - 1]][j - 1];
      }
      cin >> q;
      }
      int getlca(int x, int y)
      {
      if(dep[x] < dep[y]) swap(x, y);
      for(int j = 19; j >= 0; --j)
      {
      if(dep[fa[x][j]] >= dep[y]) x = fa[x][j];
      }
      if(x == y) return x;
      for(int j = 19; j >= 0; --j)
      {
      if(fa[x][j] != fa[y][j])
      {
      x = fa[x][j];
      y = fa[y][j];
      }
      }
      return fa[x][0];
      }
      void work()
      {
      int x, y, t;
      ll ansx, ansy;
      while(q--)
      {
      cin >> x >> y;
      if(x == y)
      {
      cout << "0 0" << endl;
      continue;
      }
      t = getlca(x, y);
      if(d[x] - d[t] == d[y] - d[t])
      {
      int tmp = x;
      for(int j = 19; j >= 0; --j)
      {
      if(dep[fa[tmp][j]] > dep[t]) tmp = fa[tmp][j];
      }
      ansx = s[tmp];
      tmp = y;
      for(int j = 19; j >= 0; --j)
      {
      if(dep[fa[tmp][j]] > dep[t]) tmp = fa[tmp][j];
      }
      ansy = s[tmp];
      cout << ansx << " " << ansy << endl;
      }
      else if(d[x] - d[t] > d[y] - d[t])
      {
      int tmpx = x, tmpy, tmp;
      for(int j = 19; j >= 0; --j)
      {
      if((dep[fa[tmpx][j]] > dep[t]) && (d[fa[tmpx][j]] + d[y] - (d[t] << 1) > d[x] - d[fa[tmpx][j]]))
      {
      tmpx = fa[tmpx][j];
      }
      }
      ansx = s[tmpx];
      tmpy = fa[tmpx][0];
      for(int j = 19; j >= 0; --j)
      {
      if((dep[fa[tmpy][j]] > dep[t]) && (d[tmpy] == d[tmpx]))
      {
      tmpy = fa[tmpy][j];
      }
      }
      tmp = tmpx;
      for(int j = 19; j >= 0; --j)
      {
      if(dep[fa[tmp][j]] > dep[tmpy]) tmp = fa[tmp][j];
      }
      ansy = s[root] - s[tmp];
      cout << ansx << " " << ansy << endl;
      }
      else
      {
      int tmpx, tmpy = y, tmp;
      for(int j = 19; j >= 0; --j)
      {
      if((dep[fa[tmpy][j]] > dep[t]) && (d[fa[tmpy][j]] + d[x] - (d[t] << 1) > d[y] - d[fa[tmpy][j]]))
      {
      tmpy = fa[tmpy][j];
      }
      }
      ansy = s[tmpy];
      tmpx = fa[tmpy][0];
      for(int j = 19; j >= 0; --j)
      {
      if((dep[fa[tmpx][j]] > dep[t]) && (d[tmpx] == d[tmpy]))
      {
      tmpx = fa[tmpx][j];
      }
      }
      tmp = tmpy;
      for(int j = 19; j >= 0; --j)
      {
      if(dep[fa[tmp][j]] > dep[tmpx]) tmp = fa[tmp][j];
      }
      ansx = s[root] - s[tmp];
      cout << ansx << " " << ansy << endl;
      }
      }
      }
      int main()
      {
      init();
      work();
      return 0;
      }

    • @ 2016-03-03 17:38:10

      那个。。t14t41t的标程貌似有误啊。。虽然确实AC了
      Pascal居然能比C++跑得快我还是很开心的~~
      我不知道你是怎么处理的,但是你看一下这组数据:
      3
      3 1 4
      3 2 4
      5 0 3
      2
      1 2
      2 3
      正确答案:
      5 0
      0 8
      你的答案:
      8 0
      0 8
      这题也就树上倍增+乱搞吧>_<
      我贴:
      type
      Rana=record
      a,b:longint
      end;
      var
      n,m,i,k,e,u,v,w:longint;
      pu,pv:int64;
      head,d,p:array[0..100005]of longint;
      s:array[0..100005]of int64;
      next,node,dist:array[0..200005]of longint;
      f:array[0..100005,0..16]of longint;
      g:array[0..100005,0..16]of int64;

      procedure ad(u,v,w:longint);
      begin
      inc(e);
      next[e]:=head[u];
      head[u]:=e;
      node[e]:=v;
      dist[e]:=w
      end;

      procedure sk(u:longint);
      var
      i,v:longint;
      begin
      s[u]:=p[u];
      for i:=1 to k do
      begin
      f[u,i]:=f[f[u,i-1],i-1]; if f[u,i]=0 then break;
      g[u,i]:=g[f[u,i-1],i-1]+g[u,i-1]
      end;
      i:=head[u];
      while i>0 do
      begin
      v:=node[i];
      if d[v]=0 then
      begin
      f[v,0]:=u;
      g[v,0]:=dist[i];
      d[v]:=d[u]+1;
      sk(v);
      inc(s[u],s[v])
      end;
      i:=next[i]
      end
      end;

      function ct(x:longint;y:int64):Rana;
      var
      i,o:longint;
      p:int64;
      begin
      p:=0;
      o:=x;
      for i:=k downto 0 do
      if (f[o,i]>0)and((p+g[o,i])<<1<=y) then
      begin
      inc(p,g[o,i]);
      o:=f[o,i]
      end;
      ct.a:=o;
      if p<<1=y then
      begin
      p:=0;
      o:=x;
      for i:=k downto 0 do
      if (f[o,i]>0)and((p+g[o,i])<<1<y) then
      begin
      inc(p,g[o,i]);
      o:=f[o,i]
      end;
      ct.b:=o
      end else ct.b:=-1
      end;

      procedure lca(u,v:longint);
      var
      i,t,x,y,z:longint;
      px,py:int64;
      R:Rana;
      begin
      x:=u; y:=v; px:=0; py:=0; z:=0;
      if d[x]<d[y] then begin t:=x; x:=y; y:=t; z:=1 end;
      t:=d[x]-d[y];
      for i:=0 to k do
      if t and(1<<i)>0 then
      begin
      inc(px,g[x,i]);
      x:=f[x,i]
      end;
      if x<>y then
      for i:=k downto 0 do
      if f[x,i]<>f[y,i] then
      begin
      inc(px,g[x,i]);
      inc(py,g[y,i]);
      x:=f[x,i];
      y:=f[y,i]
      end;
      if x<>y then
      begin
      inc(px,g[x,0]);
      inc(py,g[y,0]);
      x:=f[x,0]
      end;
      if px<py then z:=1-z;
      if z=0 then R:=ct(u,px+py)
      else R:=ct(v,px+py);
      if R.b=-1 then
      begin pu:=s[R.a]; pv:=s[1]-pu end else
      begin pu:=s[R.b];
      if px=py then begin if z=1 then R:=ct(u,px<<1)
      else R:=ct(v,px<<1);
      pv:=s[R.b] end
      else pv:=s[1]-s[R.a] end;
      if z=1 then begin px:=pu; pu:=pv; pv:=px end
      end;

      begin
      assign(input,'1935.in'); reset(input);
      assign(output,'1935.out'); rewrite(output);
      read(n);
      k:=trunc(ln(n)/ln(2)+1e-7);
      for i:=2 to n do
      begin
      read(u,v,w);
      ad(u,v,w);
      ad(v,u,w)
      end;
      for i:=1 to n do read(p[i]);
      d[1]:=1;
      sk(1);
      read(m);
      for i:=1 to m do
      begin
      read(u,v);
      if u=v then
      begin
      writeln('0 0');
      continue
      end;
      lca(u,v);
      writeln(pu,' ',pv)
      end;
      close(input); close(output)
      end.

  • 1

信息

ID
1935
难度
6
分类
a 点击显示
标签
(无)
递交数
72
已通过
19
通过率
26%
被复制
2
上传者