【NOIP 2016提高组D1T2】 天天爱跑步 Running Maverick_Frank

来源:互联网 发布:东莞金拓软件 编辑:程序博客网 时间:2024/05/19 19:34

Description

  小C同学认为跑步非常有趣,于是决定制作一款叫《天天爱跑步》的游戏。《天天爱跑步》是一个养成类游戏。需要玩家每天按时上线完成打卡任务。
  这个游戏的地图可以看作一棵包含n个节点和n-1条边的树。每条边连接两个节点,且任意两个节点存在一条路径互相可达,树上节点的编号为从1到n的正整数。
  现在有m个玩家,第i个玩家的起点为Si,终点为Ti。每天打卡任务开始时,所有玩家在第0秒同时从自己的起点出发,以每秒跑一条边的速度,不断地沿着最短路径向着自己的终点跑去,跑到终点后的玩家算是完成了打卡任务。(由于地图是一棵树,所以每个人的路径都是唯一的)
  小C想知道游戏的活跃度,所以在每个节点上都安置了一个观察员。在节点j的观察员会选择在Wj秒观察玩家,一个玩家能被这个观察员观察到当且仅当该玩家在第Wj秒也刚好到达了节点j。小C想知道每个观察员会个观察到多少人?
  注意:我们认为一个玩家到达自己的终点后该玩家就会结束游戏,他不能等待一段时间后被观察员观察到。即对于把结点j作为终点的玩家:若他正好在Wj秒前到达终点,则在节点j的观察员不能观察到该玩家;若他正好在Wj秒到达终点,则在节点j的观察员可以观察到这个玩家。

输入格式: 
第一行有两个整数n和m。其中n代表树的结点数量,同时也是观察员的数量,m代表玩家的数量。接下来n-1行每行两个整数u和v,表示结点u到结点v有一条边。接下来一行n个整数,其中第j个整数为Wj, 表示结点出现观察员的时间。接下来m行,每行两个整数Si和Ti,表示一个玩家的起点和终点。n≤300000(100%)
输出格式: 
输出1行n个整数,第i个整数表示结点i的观察员可以观察到多少人。

Sample Input

 1

6 3 
2 3 
1 2 
1 4 
4 5 
4 6 
0 2 5 1 2 3 
1 5 
1 3 
2 6 

Sample Output 1

2 0 0 1 1 1 


Sample Input 2

5 3 
1 2 
2 3 
2 4 
1 5 
0 1 0 3 0 
3 1 
1 4 
5 5 

Sample Output 2

1 2 1 0 1


Solution

这道题是2016年Noip最难的一道题,本蒟蒻就在这上面挂了。时隔一年总算把去年的坑给填了。

首先对于每个玩家的路径一定是从S到Lca(S,T)再到T,并且对于Lca以上等一些点的观察员是不可能观察到该玩家的。

将每一条路径拆分成两部分:S到Lca 和 Lca 到T。


对于一个S到Lca上的结点i,观察员i要观察到S为起点的玩家当且仅当 :Deep[S]-Deep[i]=w[i]

移项一下得到: Deep[i]+w[i]=Deep[S]  

可以注意到,对于该(每个)节点i,Deep[i]+w[i]是一个定值,因此我们需要统计以i为根的子树中深度=Deep[i]+w[i] 的以S为起点的玩家个数,并且经过该点(如何统计一会再说)。

同样的,对于一个Lca到T上的结点i,观察员i要观察到这名玩家当且仅当:Len[S,T]-(Deep[T]-Deep[i])=w[i] ,其中Len[S,T]表示S到T的路径长度 (可以自己画个图就明白了)

整理一下得到:w[i]-Deep[i]=Len[S,T]-Deep[T] 

跟第一种情况一样,我们需要统计以i为根的子树中 Len[S,T]-Deep[T]=w[i]-Deep[i]的以T为终点的玩家个数,且经过该点。


接下来讲一讲如何统计

显然的,Dfs时先访问完一个结点的所有儿子再处理该节点

以S到Lca的路径为例,可以开一个桶 Up[ ] (因为该路径是向上走的,所以叫Up,变量名无所谓了)。

这个桶记录什么呢? Up[Deep[i]+w[i]]记录当前深度=Deep[i]+w[i] 的以S为起点的玩家个数。所以对于结点i,这个点上的观察员能观察到的玩家数量就是一开始访问到这个点时的Up[Deep[i]+w[i]]与结束访问(处理完i的所有儿子)的Up[Deep[i]+w[i]] 的差值,这样子就可以避免算到无关的结点。最开始访问到i时,将i结点上的玩家数量入桶

同时应该注意,对于以i为Lca的路径{S,T},当我们结束对i的处理时,这条路径就统计完了,应该出桶,不然会对i上面的结点造成影响。

怎么实现呢? 对于一个结点i,我们记录以i为Lca的S有哪些(用邻接表或者Vector来存储),这样就可以完成出桶的操作(详见代码)

如果是Lca到T的路径,同样可以开一个桶Down[ ],以w[i]-Deep[i]为下标记录Len[S,T]-Deep[T]的以T为终点的玩家数量,该结点的观察数量同样是Down[w[i]-Deep[i]]的差值。

同时记录以i为终点T的Lca有哪些(入桶),以及  以i为Lca的T有哪些(出桶)。


Tips

一些细节:

对于Down的桶,w[i]-Deep[i] 可能是负数,超出数组的储存,因此要向右以300000个单位避免访问到负数下标

对于以退化成链的一条路径,在Lca上会重复计算(相当于有一条路径在Lca上压缩成了一个点),统计完答案之后要特判减掉就好了


复杂度分析:如果是树上倍增求Lca,复杂度为O(nlogn);如果用Tarjan求Lca,复杂度为O(n+m)。


Code

#include<algorithm>#include<iostream>#include<cstring>#include<cstdlib>#include<string>#include<cstdio>#include<vector>#include<queue>#define LL long long#define N 300024using namespace std;const int INF=0x7f7f7f7f;const int yh=300000; //Down右移struct lines{int next,to;}E[2*N];struct line1{int next,to;}E1[2*N];struct line2{int next,to;}E2[2*N];struct line3{int next,to;}E3[2*N];int f[N][21],Deep[N],n,m,S[N],T[N],Lca[N],w[N],Down[2*N],Up[N],Ans[N],Num[N];int head1[N],lcnt1,head2[N],lcnt2,head3[N],lcnt3,head[N],lcnt,x,y;int Read(){int num=0,k=1; char c=getchar();while (c<'0' || c>'9') {if (c=='-') k=-1; c=getchar();}while (c>='0' && c<='9') num=(num<<3)+(num<<1)+c-48,c=getchar();return num*k;}void Swap(int &a,int &b){a^=b; b^=a; a^=b;}void Link(int from,int to){E[++lcnt].next=head[from];E[lcnt].to=to;head[from]=lcnt;}void Link1(int from,int to){E1[++lcnt1].next=head1[from];E1[lcnt1].to=to;head1[from]=lcnt1;}void Link2(int from,int to){E2[++lcnt2].next=head2[from];E2[lcnt2].to=to;head2[from]=lcnt2;}void Link3(int from,int to){E3[++lcnt3].next=head3[from];E3[lcnt3].to=to;head3[from]=lcnt3;}void Dfs(int u,int fa){Deep[u]=Deep[fa]+1;for (int i=head[u];i;i=E[i].next)  if (E[i].to!=fa) f[E[i].to][0]=u,Dfs(E[i].to,u);}void Redouble(){for (int j=1;j<=20;j++)  for (int i=1;i<=n;i++)  f[i][j]=f[f[i][j-1]][j-1];}int Getlca(int x,int y){if (Deep[x]<Deep[y]) Swap(x,y);for (int i=20;i>=0;i--) if ((1<<i)&(Deep[x]-Deep[y])) x=f[x][i];if (x==y) return x;for (int i=20;i>=0;i--) if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];return f[x][0];}void dfs(int u,int fa){int now=Deep[u]+w[u],tmp=Up[now];for (int i=head[u];i;i=E[i].next)  if (E[i].to!=fa) dfs(E[i].to,u);Up[Deep[u]]+=Num[u];Ans[u]+=Up[now]-tmp;for (int i=head1[u];i;i=E1[i].next) Up[E1[i].to]--;}void DFS(int u,int fa){int now=w[u]-Deep[u]+yh,tmp=Down[now];for (int i=head[u];i;i=E[i].next)  if (E[i].to!=fa) DFS(E[i].to,u);for (int i=head2[u];i;i=E2[i].next) Down[yh+E2[i].to]++;Ans[u]+=Down[now]-tmp;for (int i=head3[u];i;i=E3[i].next) Down[yh+E3[i].to]--;}int  main(){n=Read(); m=Read();for (int i=1;i<n;i++) x=Read(),y=Read(),Link(x,y),Link(y,x);for (int i=1;i<=n;i++) w[i]=Read();Dfs(1,0); Redouble();  for (int i=1;i<=m;i++){  S[i]=Read(); T[i]=Read();  Lca[i]=Getlca(S[i],T[i]);  Link1(Lca[i],Deep[S[i]]);  Num[S[i]]++;  Link2(T[i],Deep[S[i]]-2*Deep[Lca[i]]);  Link3(Lca[i],Deep[S[i]]-2*Deep[Lca[i]]);}dfs(1,0); DFS(1,0);for (int i=1;i<=m;i++)  if (Deep[S[i]]-Deep[Lca[i]]==w[Lca[i]]) Ans[Lca[i]]--;for (int i=1;i<=n;i++) printf("%d ",Ans[i]);}







原创粉丝点击