[NOIP2016]天天爱跑步

洛谷·题目传送门

洛谷上这道题居然有LCT的标签......真是吓人。

实际上就是个LCA加上树上乱搞就行了。

这道题难就难在思维,实际上编程实现的难度很小,尤其是找好方法之后。

本人使用倍增LCA代码共2K,没怎么压行。

第一眼一看,每个玩家的路线都是s到t的链。

按照古老的套路,就是把 [ s 到 t ] 拆成 [ s 到根 ] + [ t 到根 ] - [ lca 到根 ] - [ lca的父亲到根 ] 四条链。

简单起见,我们把 [ lca的父亲到根 ] 也变成 [ lca到根 ] 。

这样每个点都对且只对他的祖先产生贡献。

我们统计每个点子树的贡献和就好了。

有个小bug:

在统计的时候,我们统计的是子树的贡献和,并不包含该节点。

所以如果在 s、t 和 lca 上打标记,只会影响他们的祖先节点的结果,对他们自身没有影响。

所以我们特判 s、t 和 lca 的值。

如果产生贡献(可正可负),就在统计之前先加上再说。

这样就能统计每个点的答案啦~

是不是还差了一点什么......

是否产生贡献还与时间有关。

我们设i的出发时间为st[i],点p的深度为d[p],w[i]的意义如题面所示。

对于向上走的(s到根,两条lca到根之一),若在 p 处产生贡献,则有:start_time + d [ s ] - d [ p ] = w [ p ]

移项,得:start_time + d [ s ]  = w [ p ] + d [ p ]

这样等式右边只与 p 有关。

我们可以建一个数组 h ,使 h [ start_time + d [ s ] ] ++(或--)(取决于贡献是1还是-1)

实际上对于上行的所有链,start_time = 0

我们查询一个点 p 的时候,h [ w [ p ] + d [ p ] ] 是多少,ans [ p ] 就+=多少。

下行也是类似的做法。

start_time - d [ lca ]  = w [ p ] - d [ p ]

其中start_time = d [ s ] - d [ lca ]

标记时h [ start_time - d [ lca ] ] ++(或--)

查询时 ans [ p ] += h [ w [ p ] - d [ p ] ]

一个点只对祖先产生贡献,所以我们不能直接改变 h 数组。

我们在dfs的时候,每到一个点,把关于该点的标记全部加到 h 里,再dfs该点的子节点。

dfs子节点前后答案的差值,就是子树内的贡献。

关于一个点的标记可能有很多,使用类似链式前向星的方式记录下来。

最后按点标号输出 ans [ i ] 。

  1 #include<cstdio>
  2 #include<cstring>
  3 #include<algorithm>
  4 using namespace std;
  5 
  6 int n,m;
  7 int hd[300005],nx[600005],to[600005],cnt;
  8 int w[300005];
  9 
 10 void add(int af,int at)
 11 {
 12     to[++cnt]=at;
 13     nx[cnt]=hd[af];
 14     hd[af]=cnt;
 15 }
 16 
 17 int d[300005],f[300005][25];
 18 
 19 void pre(int p,int fa)
 20 {
 21     f[p][0]=fa;
 22     d[p]=d[fa]+1;
 23     for(int i=hd[p];i;i=nx[i])
 24     {
 25         if(to[i]!=fa)pre(to[i],p);
 26     }
 27 }
 28 
 29 int lca(int x,int y)
 30 {
 31     if(d[x]<d[y])swap(x,y);
 32     for(int i=20;i>=0;i--)
 33     {
 34         if(d[f[x][i]]>=d[y])x=f[x][i];
 35     }
 36     if(x==y)return x;
 37     for(int i=20;i>=0;i--)
 38     {
 39         if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
 40     }
 41     return f[x][0];
 42 }
 43 
 44 int ct[2];
 45 int st[300005][2],nxt[600005][2],k[600005][2],val[600005][2];
 46 int ans[300005];
 47 
 48 void mark(int p,int key,int v,int dir)
 49 {
 50     val[++ct[dir]][dir]=v;
 51     k[ct[dir]][dir]=key;
 52     nxt[ct[dir]][dir]=st[p][dir];
 53     st[p][dir]=ct[dir];
 54 }
 55 
 56 int h[900005][2];
 57 
 58 void dfs(int p,int fa)
 59 {
 60     for(int no=0;no<=1;no++)
 61         for(int i=st[p][no];i;i=nxt[i][no])
 62             h[k[i][no]+n][no]+=val[i][no];
 63     int tmp=(h[w[p]+d[p]+n][0]+h[w[p]-d[p]+n][1]);
 64     for(int i=hd[p];i;i=nx[i])if(to[i]!=fa)dfs(to[i],p);
 65     ans[p]+=(h[w[p]+d[p]+n][0]+h[w[p]-d[p]+n][1]-tmp);
 66 }
 67 
 68 int main()
 69 {
 70     scanf("%d%d",&n,&m);
 71     for(int i=1;i<n;i++)
 72     {
 73         int aa,bb;
 74         scanf("%d%d",&aa,&bb);
 75         add(aa,bb);
 76         add(bb,aa);
 77     }
 78     for(int i=1;i<=n;i++)scanf("%d",&w[i]);
 79     pre(1,0);
 80     for(int i=1;i<=20;i++)
 81     {
 82         for(int j=1;j<=n;j++)
 83         {
 84             f[j][i]=f[f[j][i-1]][i-1];
 85         }
 86     }
 87     cnt=0;
 88     for(int i=1;i<=m;i++)
 89     {
 90         int s,t;
 91         scanf("%d%d",&s,&t);
 92         int l=lca(s,t);
 93         mark(s,d[s],1,0);
 94         mark(l,d[s],-1,0);
 95         mark(t,d[s]-d[l]*2,1,1);
 96         mark(l,d[s]-d[l]*2,-1,1);
 97         if(w[l]==d[s]-d[l])ans[l]--;
 98         if(w[s]==0)ans[s]++;
 99         if(w[t]==d[s]+d[t]-d[l]*2)ans[t]++;
100     }
101     dfs(1,0);
102     for(int i=1;i<=n;i++)printf("%d ",ans[i]);
103     return 0;
104 }
[NOIP2016]天天爱跑步

猜你喜欢

转载自www.cnblogs.com/eternhope/p/9498631.html