这个游戏的地图可以看作一棵包含
N
N
N个结点和
N
−
1
N-1
N−1条边的树,每条边连接两个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从
1
1
1到
N
N
N的连续正整数。
现在有
M
M
M个玩家,第个玩家的起点为
S
i
S_i
Si,终点为
T
i
T_i
Ti。每天打卡任务开始时,所有玩家在第
0
0
0秒同时从自己的起点出发,以每秒跑一条边的速度,不间断地沿着最短路径向着自己的终点跑去,跑到终点后该玩家就算完成了打卡任务。 (由于地图是一棵树,所以每个人的路径是唯一的)
小C想知道游戏的活跃度,所以在每个结点上都放置了一个观察员。 在结点
j
j
j的观察员会选择在第
W
j
W_j
Wj秒观察玩家,一个玩家能被这个观察员观察到当且仅当该玩家在第
W
j
W_j
Wj秒也理到达了结点
j
j
j。小C想知道每个观察员会观察到多少人?
注意:我们认为一个玩家到达自己的终点后该玩家就会结束游戏,他不能等待一段时间后再被观察员观察到。 即对于把结点
j
j
j作为终点的玩家:若他在第
W
j
W_j
Wj秒后到达终点,则在结点
j
j
j的观察员不能观察到该玩家;若他正好在第
W
j
W_j
Wj秒到达终点,则在结点
j
j
j的观察员可以观察到这个玩家。
Input
第一行有两个整数
N
N
N和
M
M
M 。其中
N
N
N代表树的结点数量,同时也是观察员的数量,
M
M
M代表玩家的数量。
接下来
N
−
1
N - 1
N−1行每行两个整数
U
U
U和
V
V
V,表示结点
U
U
U到结点
V
V
V有一条边。
接下来一行
N
N
N个整数,其中第个整数为
W
j
W_j
Wj,表示结点出现观察员的时间。
接下来
M
M
M行,每行两个整数
S
i
S_i
Si和
T
i
T_i
Ti,表示一个玩家的起点和终点。
对于所有的数据,保证
1
≤
S
i
,
T
i
≤
N
,
0
≤
W
j
≤
N
1 \le S_i,T_i \le N,0 \le W_j \le N
1≤Si,Ti≤N,0≤Wj≤N。
对于前
5
5
5个测试点,直接暴搜出每一条
S
i
→
T
i
S_i \to T_i
Si→Ti路径,
O
(
n
m
)
O(nm)
O(nm)暴力统计即可
对于第
9
9
9~
12
12
12个测试点,因为所有的
S
i
=
1
S_i = 1
Si=1,我们令
1
1
1为树根,记
s
u
m
[
x
]
sum[x]
sum[x]表示以
x
x
x为根的子树中
T
i
T_i
Ti的个数,这样当遍历到任意一点
x
x
x时,就共有
s
u
m
[
x
]
sum[x]
sum[x]个玩家在这一时刻跑到了点
x
x
x,直接判断加入答案即可,复杂度为
O
(
n
)
O(n)
O(n)
Code-1
#include<iostream>#include<cstdio>usingnamespace std;constint N =3e5+5, M =6e5+5;int w[N], Ans[N], Del[N];bool vis[N], f;int n, m, s[N], t[N];structEdge{int to; Edge *nxt;}a[M],*T = a,*lst[N];inlineintget(){char ch;int res;while((ch =getchar())<'0'|| ch >'9');
res = ch -'0';while((ch =getchar())>='0'&& ch <='9')
res =(res <<3)+(res <<1)+ ch -'0';return res;}inlinevoidput(int x){if(x >9)put(x /10);putchar(x %10+48);}inlinevoidaddEdge(constint&x,constint&y){(++T)->nxt = lst[x]; lst[x]= T; T->to = y;(++T)->nxt = lst[y]; lst[y]= T; T->to = x;}inlinevoidDfs1(constint&x,constint&I){if(x == t[I])return(void)(f =true);int y;for(Edge *e = lst[x]; e; e = e->nxt)if(!vis[y = e->to]){
vis[y]=true;Dfs1(y, I);if(f)return;
vis[y]=false;}}inlinevoidDfs2(constint&x,constint&st){if(w[x]== st) Ans[x]++;int y;for(Edge *e = lst[x]; e; e = e->nxt)if(vis[y = e->to]){
vis[y]=false;Dfs2(y, st +1);}}inlinevoidDfs3(constint&x,constint&fa,constint&st){if(w[x]== st) Ans[x]+= Del[x];int y;for(Edge *e = lst[x]; e; e = e->nxt)if((y = e->to)!= fa)Dfs3(y, x, st +1);}inlinevoidDfs4(constint&x,constint&fa){int y;for(Edge *e = lst[x]; e; e = e->nxt)if((y = e->to)!= fa){Dfs4(y, x);
Del[x]+= Del[y];}}intmain(){
n =get(); m =get();for(int i =1; i < n;++i)addEdge(get(),get());for(int i =1; i <= n;++i) w[i]=get();for(int i =1; i <= m;++i) s[i]=get(), t[i]=get();if(n <=993){for(int i =1; i <= m;++i){
f =false;
vis[s[i]]=true;Dfs1(s[i], i);
vis[s[i]]=false;Dfs2(s[i],0);}}else{for(int i =1; i <= m;++i) Del[t[i]]++;Dfs4(1,0);Dfs3(1,0,0);}for(int i =1; i <= n;++i)put(Ans[i]),putchar(' ');}
【100pts】LCA + 树上差分 + 桶维护子树信息
思路一直跟着人跑显然是会
T
L
E
TLE
TLE的,我们考虑把问题进行转化:
我们先求出
S
i
,
T
i
S_i,T_i
Si,Ti的最近公共祖先记作
L
C
A
i
LCA_i
LCAi,因为
S
i
→
L
C
A
i
S_i \to LCA_i
Si→LCAi和
L
C
A
i
→
T
i
LCA_i \to T_i
LCAi→Ti两段路径跑的方向不同,我们考虑拆开分别求这两种路径上的观察员观察到的情况,如果点
L
C
A
i
LCA_i
LCAi重复算了两遍再扣除掉。
对于路径
S
i
→
L
C
A
i
S_i \to LCA_i
Si→LCAi上的观察员(点)
x
x
x,当他能观察到玩家
i
i
i时必然满足
d
e
e
p
[
x
]
+
W
x
=
d
e
e
p
[
S
i
]
deep[x] + W_x = deep[S_i]
deep[x]+Wx=deep[Si],那么问题就被转化为在以
x
x
x为根的子树内,有多少个玩家满足
d
e
e
p
[
x
]
+
W
x
=
d
e
e
p
[
S
i
]
deep[x] + W_x = deep[S_i]
deep[x]+Wx=deep[Si]并且
x
x
x在路径
S
i
→
L
C
A
i
S_i \to LCA_i
Si→LCAi上。
这样似乎仍不好解决,我们再记一个桶
D
n
u
m
[
j
]
Dnum[j]
Dnum[j],表示满足
d
e
e
p
[
S
i
]
=
j
deep[S_i] = j
deep[Si]=j并且当前遍历到的点
x
x
x在路径
S
i
→
L
C
A
i
S_i \to LCA_i
Si→LCAi上的玩家个数,那么我们每次遍历到一个点,只要查询一下对应的
D
n
u
m
[
d
e
e
p
[
x
]
+
W
x
]
Dnum[deep[x] + W_x]
Dnum[deep[x]+Wx]就可以了(
d
e
e
p
[
x
]
+
W
x
deep[x] + W_x
deep[x]+Wx可能会超出原树的最大深度,注意判断)。
考虑怎么统计
D
n
u
m
[
j
]
Dnum[j]
Dnum[j](对于所有的路径
S
i
→
L
C
A
i
S_i \to LCA_i
Si→LCAi):
因为要只算到以
x
x
x为根的子树内,
D
n
u
m
Dnum
Dnum再开一维空间显然也不够,那我们只能对于所有子树共用一个数组,在遍历以
x
x
x为根的子树之前,先存储下当前的
D
n
u
m
[
d
e
e
p
[
x
]
+
W
x
]
Dnum[deep[x] + W_x]
Dnum[deep[x]+Wx],遍历完后再与原来的相减才能加入最后的答案中。
每次遍历到任意一点
x
x
x,我们必然要令
D
n
u
m
[
d
e
e
p
[
x
]
]
Dnum[deep[x]]
Dnum[deep[x]]加上
S
i
=
x
S_i = x
Si=x的玩家个数;查询完
D
n
u
m
[
d
e
e
p
[
x
]
+
W
x
]
Dnum[deep[x] + W_x]
Dnum[deep[x]+Wx]后,同样也要令每一个
L
C
A
i
=
x
LCA_i = x
LCAi=x的玩家的
D
n
u
m
[
d
e
e
p
[
S
i
]
]
Dnum[deep[S_i]]
Dnum[deep[Si]]减一。
对于路径
L
C
A
i
→
T
i
LCA_i \to T_i
LCAi→Ti,我们用同样的方法统计。这时候观察员
x
x
x观察到玩家
i
i
i的条件就为
d
e
e
p
[
T
i
]
−
d
e
e
p
[
x
]
=
L
e
n
i
−
W
x
deep[T_i] - deep[x] = Len_i - W_x
deep[Ti]−deep[x]=Leni−Wx,也就是
d
e
e
p
[
x
]
−
W
x
=
d
e
e
p
[
T
i
]
−
l
e
n
i
deep[x] - W_x = deep[T_i] - len_i
deep[x]−Wx=deep[Ti]−leni。
那么这时每次遍历到任意一点
x
x
x,我们就要令每一个
T
i
=
x
T_i = x
Ti=x的玩家的
D
n
u
m
[
d
e
e
p
[
T
i
]
−
l
e
n
i
]
Dnum[deep[T_i] - len_i]
Dnum[deep[Ti]−leni]加一(这里
D
n
u
m
Dnum
Dnum数组的意义与之前对于路径
S
i
→
L
C
A
i
S_i \to LCA_i
Si→LCAi统计时有所不同,但也只是用一个桶统计对应的玩家个数),查询完
D
n
u
m
[
d
e
e
p
[
x
]
−
W
x
]
Dnum[deep[x] - W_x]
Dnum[deep[x]−Wx]后处理
L
C
A
i
=
x
LCA_i = x
LCAi=x的玩家也是同理(
d
e
e
p
[
T
i
]
−
l
e
n
i
deep[T_i] - len_i
deep[Ti]−leni可能为负数,因此可以统一加上
3
×
1
0
5
3 \times 10^5
3×105令其为正)。
以下代码用
T
a
r
j
a
n
Tarjan
Tarjan求
L
C
A
LCA
LCA,因此总复杂度为
O
(
n
+
m
)
O(n + m)
O(n+m)(并查集复杂度忽略不计)
Code-2
#include<iostream>#include<cstdio>#include<cstring>usingnamespace std;constint N =3e5+5, M = N <<1;int lca[N], Ans[N], st[N], ed[N], fa[N], dep[N];int n, m, D, w[N], Dnu[M], num[N];bool vis[N];char frd[N],*hed = frd + N;char fwt[M <<1],*opt = fwt;constchar*tal = hed;inlinecharnxtChar(){if(hed == tal)fread(frd,1, N,stdin), hed = frd;return*hed++;}inlineintget(){char ch;int res =0;while((ch =nxtChar())<'0'|| ch >'9');
res = ch ^48;while((ch =nxtChar())>='0'&& ch <='9')
res =(res <<3)+(res <<1)+(ch ^48);return res;}inlinevoidput(int x){if(x >9)put(x /10);*opt++= x %10+48;}structEdge{int to; Edge *nxt;};structQuery{int wit, num; Query *nxt;};
Edge p[M],*T = p,*lst[N];
Edge a[N],*A = a,*Ast[N];
Edge b[N],*B = b,*Bst[N];
Edge c[N],*C = c,*Cst[N];
Query q[M],*Q = q,*rst[N];inlinevoidAddEdge(constint&x,constint&y){(++T)->nxt = lst[x]; lst[x]= T; T->to = y;(++T)->nxt = lst[y]; lst[y]= T; T->to = x;}inlinevoidAskEdge(constint&x,constint&y,constint&I){(++Q)->nxt = rst[x]; rst[x]= Q; Q->wit = y; Q->num = I;(++Q)->nxt = rst[y]; rst[y]= Q; Q->wit = x; Q->num = I;}inlinevoidAjnEdge(constint&x,constint&y){(++A)->nxt = Ast[x]; Ast[x]= A; A->to = y;}inlinevoidBjnEdge(constint&x,constint&y){(++B)->nxt = Bst[x]; Bst[x]= B; B->to = y;}inlinevoidCjnEdge(constint&x,constint&y){(++C)->nxt = Cst[x]; Cst[x]= C; C->to = y;}inlinevoidCkMax(int&x,constint&y){if(x < y) x = y;}inlineintFind(constint&x){if(fa[x]!= x) fa[x]=Find(fa[x]);return fa[x];}inlinevoidTarjan(constint&x,constint&F){CkMax(D, dep[x]= dep[F]+1); fa[x]= x;int y, z;for(Edge *e = lst[x]; e; e = e->nxt)if((y = e->to)!= F)Tarjan(y, x), fa[y]= x;
vis[x]=true;for(Query *e = rst[x]; e; e = e->nxt)if(vis[y = e->wit]&&!lca[z = e->num]) lca[z]=Find(y);}inlinevoidDfs1(constint&x,constint&F){int u = dep[x]+ w[x], v, y;if(u <= D) v = Dnu[u];for(Edge *e = lst[x]; e; e = e->nxt)if((y = e->to)!= F)Dfs1(y, x);
Dnu[dep[x]]+= num[x];if(u <= D) Ans[x]+= Dnu[u]- v;for(Edge *e = Ast[x]; e; e = e->nxt) Dnu[e->to]--;}inlinevoidDfs2(constint&x,constint&F){int u = dep[x]- w[x]+ N, v = Dnu[u], y;for(Edge *e = lst[x]; e; e = e->nxt)if((y = e->to)!= F)Dfs2(y, x);for(Edge *e = Bst[x]; e; e = e->nxt) Dnu[e->to]++;
Ans[x]+= Dnu[u]- v;for(Edge *e = Cst[x]; e; e = e->nxt) Dnu[e->to]--;}intmain(){
n =get(); m =get();for(int i =1; i < n;++i)AddEdge(get(),get());for(int i =1; i <= n;++i) w[i]=get();for(int i =1; i <= m;++i){
st[i]=get(); ed[i]=get(); num[st[i]]++;AskEdge(st[i], ed[i], i);}Tarjan(1,0);for(int i =1; i <= m;++i){int v = dep[st[i]]+ dep[ed[i]]-(dep[lca[i]]<<1);AjnEdge(lca[i], dep[st[i]]);BjnEdge(ed[i], dep[ed[i]]- v + N);CjnEdge(lca[i], dep[ed[i]]- v + N);}Dfs1(1,0);memset(Dnu,0,sizeof(Dnu));Dfs2(1,0);for(int i =1; i <= m;++i)if(dep[st[i]]== dep[lca[i]]+ w[lca[i]]) Ans[lca[i]]--;for(int i =1; i < n;++i)put(Ans[i]),*opt++=' ';put(Ans[n]);fwrite(fwt,1, opt - fwt,stdout);}