bzoj4719: [Noip2016]天天愛跑步 樹上差分
Description
小c同學認為跑步非常有趣,於是決定制作一款叫做《天天愛跑步》的遊戲。?天天愛跑步?是一個養成類遊戲,需要
玩家每天按時上線,完成打卡任務。這個遊戲的地圖可以看作一一棵包含 N個結點和N-1 條邊的樹, 每條邊連接兩
個結點,且任意兩個結點存在一條路徑互相可達。樹上結點編號為從1到N的連續正整數。現在有個玩家,第個玩家的
起點為Si ,終點為Ti 。每天打卡任務開始時,所有玩家在第0秒同時從自己的起點出發, 以每秒跑一條邊的速度,
不間斷地沿著最短路徑向著自己的終點跑去, 跑到終點後該玩家就算完成了打卡任務。 (由於地圖是一棵樹, 所以
每個人的路徑是唯一的)小C想知道遊戲的活躍度, 所以在每個結點上都放置了一個觀察員。 在結點的觀察員會選
擇在第Wj秒觀察玩家, 一個玩家能被這個觀察員觀察到當且僅當該玩家在第Wj秒也理到達了結點J 。 小C想知道
每個觀察員會觀察到多少人?註意: 我們認為一個玩家到達自己的終點後該玩家就會結束遊戲, 他不能等待一 段時
間後再被觀察員觀察到。 即對於把結點J作為終點的玩家: 若他在第Wj秒重到達終點,則在結點J的觀察員不能觀察
到該玩家;若他正好在第Wj秒到達終點,則在結點的觀察員可以觀察到這個玩家。
Input
第一行有兩個整數N和M 。其中N代表樹的結點數量, 同時也是觀察員的數量, M代表玩家的數量。
接下來n-1 行每行兩個整數U和V ,表示結點U 到結點V 有一條邊。
接下來一行N 個整數,其中第個整數為Wj , 表示結點出現觀察員的時間。
接下來 M行,每行兩個整數Si和Ti,表示一個玩家的起點和終點。
對於所有的數據,保證 。
1<=Si,Ti<=N,0<=Wj<=N
Output
輸出1行N 個整數,第個整數表示結點的觀察員可以觀察到多少人。
首先可以得到兩個式子
\(dep[u]-dep[x]=w[x]\)
\(dep[u]-2*dep[lca]+dep[x]=w[x]\)
換位得到
\(w[x]+dep[x]=dep[u]\)
\(w[x]-dep[x]=dep[u]-2*dep[lca]\)
\(x\)的值確定的時候,左邊的值是確定的,因此我們考慮用x的子樹更新\(x\)的答案。
用兩個桶記錄值,每\(dfs\)到一個點更新桶的值的個數。
用\(dep[u]\)和\(dep[u]-2*dep[lca]\)更新桶的情況,
用\(w[x]+dep[x]\)和\(w[x]-dep[x]\)查詢\(dfs\)到\(x\)點時的答案。
註意\(w[x]-dep[x]\)的答案可能為負,所以要加上\(maxn\)
回溯時更新當前節點的答案,答案更新時減去父親節點的答案
對於\(lca\)
另外,對於分裂成兩條鏈\(lca\)可能會被統計兩遍,最後特殊判斷一下,如果被統計了兩遍就減去一遍,
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<vector>
const int maxn=3e5+10;
const int k=448;
#define For(i,a,b) for(int i=(a);i<=(b);i++)
#define Dec(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch==‘-‘)f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-‘0‘;ch=getchar();}
return x*f;
}
int n,m;
int fa[maxn],w[maxn],s[maxn],t[maxn];
vector<int>mp[maxn];
int son[maxn],size[maxn],top[maxn],dep[maxn];
struct que{int v,tag;};
vector<que>w1[maxn];
vector<que>w2[maxn];
int ans[maxn],lca[maxn];
void dfs1(int x,int f,int d){
fa[x]=f;
dep[x]=d;
size[x]=1;
for(int i=0;i<(int)mp[x].size();i++){
int u=mp[x][i];
if(u==f)continue;
dfs1(u,x,d+1);
size[x]+=size[u];
if(!son[x]||size[son[x]]<size[u])son[x]=u;
}
}
void dfs2(int x,int t){
top[x]=t;
if(!son[x])return ;
dfs2(son[x],t);
for(int i=0;i<(int)mp[x].size();i++){
int u=mp[x][i];
if(u==son[x]||u==fa[x])continue;
dfs2(u,u);
}
}
int LCA(int x,int y){
int fx=top[x],fy=top[y];
while(fx!=fy){
if(dep[fx]>dep[fy])x=fa[fx];
else y=fa[fy];
fx=top[x];fy=top[y];
}
if(dep[x]>dep[y])return y;
else return x;
}
int bac1[2*maxn],bac2[2*maxn];
void dfs(int x,int a,int b){
for(int i=0;i<(int)w1[x].size();i++)
// For(i,0,(int)w1[x].size()-1) //不強制轉換會RE
bac1[w1[x][i].v+maxn]+=w1[x][i].tag;//更新桶內情況
for(int i=0;i<(int)w2[x].size();i++)
// For(i,0,(int)w2[x].size()-1)
bac2[w2[x][i].v+maxn]+=w2[x][i].tag;//更新桶內情況
for(int i=0;i<(int)mp[x].size();i++){
int u=mp[x][i];
if(u==fa[x])continue;
dfs(u,bac1[dep[u]+w[u]+maxn],bac2[w[u]-dep[u]+maxn]);
}
ans[x]+=bac1[dep[x]+w[x]+maxn]+bac2[w[x]-dep[x]+maxn]-a-b;//a,b是父親節點的答案,要減去父親節點的情況就是當前節點的結果
}
int main()
{
n=read();m=read();
For(i,1,n-1){
int u=read(),v=read();
mp[u].push_back(v);
mp[v].push_back(u);
}
For(i,1,n)w[i]=read();
For(i,1,m)s[i]=read(),t[i]=read();
dfs1(1,0,0);
dfs2(1,1);
For(i,1,m)lca[i]=LCA(s[i],t[i]);
For(i,1,m){
if(lca[i]==t[i]){
w1[s[i]].push_back((que){dep[s[i]],1});
w1[fa[t[i]]].push_back((que){dep[s[i]],-1});
}
else if(lca[i]==s[i]){
w2[t[i]].push_back((que){dep[s[i]]-2*dep[lca[i]],1});
w2[fa[s[i]]].push_back((que){dep[s[i]]-2*dep[lca[i]],-1});
}
else{
if(w[lca[i]]+dep[lca[i]]==dep[s[i]])--ans[lca[i]];
w1[s[i]].push_back((que){dep[s[i]],1});
w1[fa[lca[i]]].push_back((que){dep[s[i]],-1});
w2[t[i]].push_back((que){dep[s[i]]-2*dep[lca[i]],1});
w2[fa[lca[i]]].push_back((que){dep[s[i]]-2*dep[lca[i]],-1});
}
}
dfs(1,0,0);
For(i,1,n)printf("%d ",max(0,ans[i]));
return 0;
}
bzoj4719: [Noip2016]天天愛跑步 樹上差分