题面不好化简。
规定
%
为了方便,先钦定一个点为根节点,这里用1号点,并且规定一些符号。
W
u
:
W_u:
Wu:点
u
u
u 的观测时间。
l
c
a
(
A
,
B
)
:
lca(A,B):
lca(A,B): 点
A
A
A 和点
B
B
B 的最近公共祖先。
A
→
B
:
A\rightarrow B:
A→B: 点
A
A
A 到点
B
B
B 的(树上唯一)路径。
F
u
:
F_u:
Fu: 点
u
u
u 的父亲。
d
e
p
u
:
dep_u:
depu: 点
u
u
u 的深度。
a
n
s
u
:
ans_u:
ansu: 点
u
u
u 最终的答案。
d
i
s
(
A
,
B
)
:
dis(A,B):
dis(A,B):
A
→
B
A\rightarrow B
A→B 的路径长度。
链:深度严格递增或递减的路径。
题解
% 可以发现,对于一个点,如果要考虑所有边对其贡献会非常困难。因此我们开始考虑对于每条边,求出对路径上的点的贡献。我们首先把一条 S → T S\rightarrow T S→T 的路径拆开,拆成 S → l c a ( S , T ) → T S\rightarrow lca(S,T)\rightarrow T S→lca(S,T)→T ,进一步拆开变成 S → l c a ( S , T ) S\rightarrow lca(S,T) S→lca(S,T) 和 l c a ( S , T ) → T lca(S,T)\rightarrow T lca(S,T)→T,然后分别考虑这两部分对答案的贡献,最后减去计算重复的 l c a ( S , T ) lca(S,T) lca(S,T)。
向上的链
% 先来考虑 S → l c a ( S , T ) S\rightarrow lca(S,T) S→lca(S,T),根据题意,点 u u u 能观测到这条路径当且仅当点 u u u 在 S → l c a ( S , T ) S\rightarrow lca(S,T) S→lca(S,T) 上,且 W u = d e p S − d e p u W_u=dep_S-dep_u Wu=depS−depu,即当花费 W u W_u Wu 的时间后恰好到达点 u u u,移项,得 d e p S = d e p u + W u dep_S=dep_u+W_u depS=depu+Wu
% 换言之,对于点 u u u,所有对 a n s u ans_u ansu 产生贡献的链 S → l c a ( S , T ) S\rightarrow lca(S,T) S→lca(S,T) 必须同时满足以下三点。
- d e p S = d e p u + W u dep_S=dep_u+W_u depS=depu+Wu
- S S S 属于以 u u u 为根的子树
- S → l c a ( S , T ) S\rightarrow lca(S,T) S→lca(S,T) 路径对应的 d e p l c a ( S , T ) < d e p u dep_{lca(S,T)}<dep_u deplca(S,T)<depu,即路径还没有结束。
%
因此,我们只需要统计以
u
u
u 为根的子树中,有多少个点的
d
e
p
dep
dep 值等于
d
e
p
u
+
W
u
dep_u+W_u
depu+Wu。
怎么求?我们考虑在深度优先搜索的时候,记录下
U
p
[
i
]
Up[i]
Up[i],表示已经遍历过的点中,有多少条没有结束的路径,满足路径开端的
d
e
p
dep
dep 值等于
i
i
i。那么如果我们要求子树内有多少个点满足
d
e
p
dep
dep 值等于
i
i
i,只需要计算一下遍历子树前和遍历子树后
U
p
[
d
e
p
u
+
W
u
]
Up[dep_u+W_u]
Up[depu+Wu] 的差值即可。
如此,在遇到一条路径的开端时,将其按照
d
e
p
dep
dep 值加入
U
p
Up
Up 数组中,在遇到路径结束时,在
U
p
Up
Up 数组中删除这条路径开端的
d
e
p
dep
dep 值(注意不是删除路径末端的
d
e
p
dep
dep 值)。
//求向上的链的答案
int Up[maxn],ans[maxn];
void dfst(int u){
int c=Up[dep[u]+w[u]];//记录初始值
for(int i=head[u];i;i=edges[i].next){
int v=edges[i].v;
//Fa[u][k]表示点u的第2^j个父亲(倍增求lca用)
if(v==Fa[u][0]) continue;
dfst(v);
}
ans[u]+=Up[dep[u]+w[u]]-c;//增量为子树中满足dep值等于dep[u]+w[u]的数量
//更新Up数组
for(int i=qs[u];i;i=edges[i].next){//将开端和结束信息拉链成一个邻接表
//此处edges[i].v==1表示这是一条路径开端
//否则说明是一条路径的末尾
//edges[i].qidian表示路径开端的点的编号
if(edges[i].v==1) Up[dep[u]]++;//加上点u的贡献
else Up[dep[edges[i].qidian]]--;//删除当初的点u的贡献
}
}
向下的链
% 然后我们来考虑向下的链 l c a ( S , T ) → T lca(S,T)\rightarrow T lca(S,T)→T,根据题意,若点 u u u 能观测到这条路径当且仅当点 u u u 在 l c a ( S , T ) → T lca(S,T)\rightarrow T lca(S,T)→T 上,且 W u = d i s ( S , T ) − ( d e p T − d e p u ) W_u=dis(S,T)-(dep_T-dep_u) Wu=dis(S,T)−(depT−depu),化简得 d e p u − W u = d e p T − d i s ( S , T ) dep_u-W_u=dep_T-dis(S,T) depu−Wu=depT−dis(S,T)
% 因此,类似地,对于点 u u u,所有对 a n s u ans_u ansu 产生贡献的链 l c a ( S , T ) → T lca(S,T)\rightarrow T lca(S,T)→T 必须同时满足以下三点。
- d e p u − W u = d e p T − d i s ( S , T ) dep_u-W_u=dep_T-dis(S,T) depu−Wu=depT−dis(S,T)
- T T T 属于以 u u u 为根的子树
- d e p l c a ( S , T ) < d e p u dep_{lca(S,T)}<dep_u deplca(S,T)<depu,即 l c a ( S , T ) → T lca(S,T)\rightarrow T lca(S,T)→T 已经开始。
%
类似地深度优先搜索的时,记录下
D
n
[
i
]
Dn[i]
Dn[i],表示已经遍历过的点中,有多少条已经开始的路径,满足路径末端的
d
e
p
dep
dep 值减去路径长度等于
i
i
i。求子树信息仍然计算差值即可。
D
n
Dn
Dn 数组的更新也类似。唯一需要注意的是
d
e
p
u
−
W
u
dep_u-W_u
depu−Wu 的值可能为负,因此需要对数组偏移一下,下面的代码没有采用访问时偏移的方法,而是采用了指针整体偏移的方式。
int _dn[maxn<<1];
int *Dn=&_dn[maxn];//指针放在中间位置
void dfed(int u){
int c=Dn[dep[u]-w[u]];//记录初始值
for(int i=head[u];i;i=edges[i].next){
int v=edges[i].v;
if(v==Fa[u][0]) continue;
dfed(v);
}
ans[u]+=Dn[dep[u]-w[u]]-c;//增量为子树中满足要求的总数
for(int i=qs[u];i;i=edges[i].next){
if(edges[i].v==1) Dn[dep[u]-edges[i].w]++;//是路径开端则加上点u的贡献
else Dn[dep[edges[i].qidian]-edges[i].w]--;//删除当初的点u的贡献
}
}
重复部分
%
上面考虑了
S
→
l
c
a
(
S
,
T
)
S\rightarrow lca(S,T)
S→lca(S,T) 和
l
c
a
(
S
,
T
)
→
T
lca(S,T)\rightarrow T
lca(S,T)→T 两条链,因此
l
c
a
(
S
,
T
)
lca(S,T)
lca(S,T) 被考虑了两次,因而需要减去一次。
路径
S
i
→
T
j
S_i\rightarrow T_j
Si→Tj 能对
a
n
s
l
c
a
(
S
,
T
)
ans_{lca(S,T)}
anslca(S,T) 产生贡献当且仅当
W
l
c
a
(
S
,
T
)
=
d
e
p
S
−
d
e
p
l
c
a
(
S
,
T
)
W_{lca(S,T)}=dep_{S}-dep_{lca(S,T)}
Wlca(S,T)=depS−deplca(S,T) 减去即可。
//LCA[i]=lca(S[i],T[i])
for(int i=1;i<=m;i++) if(dep[S[i]]-w[LCA[i]]==dep[LCA[i]]) --ans[LCA[i]];
完整代码
#include<bits/stdc++.h>
using namespace std;
#define maxn 1000010
struct edge{
int v,next,w,qidian;
//在图head意义下,v:端点 w,qidian: 无意义
//在询问qs意义下,v:链的开始为1,链的结束为-1
//w:点对距离 qidian:询问的另一个端点
}edges[maxn<<1];
int n,head[maxn],cnt=0;
//加入图中的边
void ins(int u,int v){
edges[++cnt]=(edge){v,head[u]};
head[u]=cnt;
}
//加入点对信息
int qs[maxn];
//op:链的开始(1)或者结束(-1)
void qns(int u,int op,int w=0,int qidian=0){
edges[++cnt]=(edge){op,qs[u],w,qidian};
qs[u]=cnt;
}
//求出深度,父亲
int w[maxn],dep[maxn],Fa[maxn][20];
void predfs(int u,int fa=0){
dep[u]=dep[fa]+1; Fa[u][0]=fa;
for(int i=head[u];i;i=edges[i].next){
int v=edges[i].v;
if(v==fa) continue;
predfs(v,u);
}
}
//倍增求最近公共祖先
int lca(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
for(int i=19;i>=0&&dep[x]!=dep[y];i--)
if(dep[Fa[x][i]]>=dep[y]) x=Fa[x][i];
if(x==y) return x;
for(int i=19;i>=0;i--)
if(Fa[x][i]!=Fa[y][i]){
x=Fa[x][i];
y=Fa[y][i];
}
return Fa[x][0];
}
//求距离
int dis(int u,int v){
int lcas=lca(u,v);
return dep[u]+dep[v]-dep[lcas]*2;
}
//求向上的链的答案
int Up[maxn],ans[maxn];
void dfst(int u){
int c=Up[dep[u]+w[u]];//记录初始值
for(int i=head[u];i;i=edges[i].next){
int v=edges[i].v;
if(v==Fa[u][0]) continue;
dfst(v);
}
ans[u]+=Up[dep[u]+w[u]]-c;//增量为子树中满足要求的总数
for(int i=qs[u];i;i=edges[i].next){
if(edges[i].v==1) Up[dep[u]]++;//加上点u的贡献
else Up[dep[edges[i].qidian]]--;//删除当初的点u的贡献
}
}
//求向下的链的答案
int _dn[maxn<<1];
int *Dn;
void dfed(int u){
int c=Dn[dep[u]-w[u]];//记录初始值
for(int i=head[u];i;i=edges[i].next){
int v=edges[i].v;
if(v==Fa[u][0]) continue;
dfed(v);
}
ans[u]+=Dn[dep[u]-w[u]]-c;//增量为子树中满足要求的总数
for(int i=qs[u];i;i=edges[i].next){
if(edges[i].v==1) Dn[dep[u]-edges[i].w]++;//加上点u的贡献
else Dn[dep[edges[i].qidian]-edges[i].w]--;//删除当初的点u的贡献
}
}
int S[maxn],T[maxn],LCA[maxn];
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1,a,b;i<n;i++){
scanf("%d%d",&a,&b);
ins(a,b);ins(b,a);
}
for(int i=1;i<=n;i++)
scanf("%d",&w[i]);
//预处理信息
predfs(1);
for(int j=1;j<=19;j++)
for(int i=1;i<=n;i++)
Fa[i][j]=Fa[Fa[i][j-1]][j-1];
//处理向上的链
for(int i=1;i<=m;i++){
scanf("%d%d",&S[i],&T[i]);
LCA[i]=lca(S[i],T[i]);//求lca
ans[S[i]]+=(w[S[i]]==0);//当链长度为0时特判
qns(S[i],1);//加入链信息
qns(LCA[i],-1,0,S[i]);//在lca处删除链信息
}dfst(1);
//重新处理向下的链
memset(qs,0,sizeof qs);
for(int i=1;i<=m;i++){
int dist=dis(S[i],T[i]);
ans[T[i]]+=(w[T[i]]==dist);//当链长度为0时特判
qns(T[i],1,dist);//加入链信息
qns(LCA[i],-1,dist,T[i]);//在lca处删除链信息
}
Dn=&_dn[maxn];//为了适应负数的情况
dfed(1);
//去除lca被重复计算的部分
for(int i=1;i<=m;i++)
if(dep[S[i]]-w[LCA[i]]==dep[LCA[i]])
--ans[LCA[i]];
for(int i=1;i<=n;i++)
printf("%d ",ans[i]);
return 0;
}