題面不好化簡。
規定
% 為了友善,先欽定一個點為根節點,這裡用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;
}