[Noip2016]天天愛跑步
阿新 • • 發佈:2020-08-15
[Noip2016]天天愛跑步
一.前言
啊啊啊進度趕不上了……題目連結
二.思路
首先做過一道相似的題。觀察員 j 能觀察到要分為兩種情況。在 \(s_i\) 到 \(lca\) 這一段路程上,要滿足 \(w_j=dep_{s_i}-dep_j\),在 \(t_i\) 到 \(lca\) 這一段,要滿足 \(w_j=dep_{s_i}-dep_{lca}+dep_{j}-dep_{lca}\),兩個都可以一項使得帶 j 的都在一邊,這樣既可以使得一邊可以靠 j 的本身屬性算出來,另一邊也只被計劃相關所覆蓋。
由於每一個觀察員都要求,站在計劃的角度再去計算對每個觀察員是否有貢獻實屬不太現實。試著站在觀察員的角度去計算貢獻。很容易能發現能對觀察員 j 做出貢獻的,滿足計劃的 lca 是 j 或者 j 的祖先,並且 起始/結束點 在 j 的子樹裡面。
並不在意是那些計劃對於 j 做出貢獻,只關注貢獻數就好。於是使用dfs,後序遍歷,全域性桶來儲存當前可能構成答案的點的個數,具體來講,一個桶儲存 \(dep_i\) 另一個儲存 \(dep_{s_i}-2*dep_{lca}\),(這些都是通過最上面的式子移項得到,後一個有可能為負需要偏移),到時候直接加上桶內對應數值就好。
接下來是一些關於桶的事項:
-
答案是遍歷子樹前與後桶內的差值
都不是你子樹內貢獻的你加甚麼!
-
當遍歷完當前點,要把當前點作為 lca 的相關計劃都減去
看程式碼吧,(開了許多無用空間qwq),要注意由於拆成兩條邊計算,如果 lca 滿足會重複一次,記得特判減去。
#include<iostream> #include<cstdio> #include<algorithm> #include<fstream> #include<cmath> #include<vector> using namespace std; int read(){ char ch=getchar(); int res=0,f=1; for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1; for(;ch>='0'&&ch<='9';ch=getchar())res=res*10+(ch-'0'); return res*f; } const int MAXN=300001; int n,m,w[MAXN],s[MAXN],t[MAXN],ans[MAXN],lca[MAXN]; int head[MAXN],ne[2*MAXN],to[2*MAXN],dis[2*MAXN],tot; void add(int x,int y){to[++tot]=y,ne[tot]=head[x],head[x]=tot;} int dep[MAXN],f[MAXN][18]; void dfs(int x,int fa){ dep[x]=dep[fa]+1; f[x][0]=fa; for(int i=1;i<18;++i)f[x][i]=f[f[x][i-1]][i-1]; for(int i=head[x];i;i=ne[i]){ if(to[i]==fa)continue; dfs(to[i],x); } } int LCA(int x,int y){ if(dep[x]<dep[y])swap(x,y); for(int i=17;i>=0;--i)if(dep[f[x][i]]>=dep[y])x=f[x][i]; if(x==y)return x; for(int i=17;i>=0;--i)if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i]; return f[x][0]; } vector <pair<int,int> > q[MAXN]; vector <pair<int,int> > sumr[MAXN]; int alf1[MAXN*2],alf2[MAXN*3],suml[MAXN]; int solve(int x,int fa){ int t1=alf1[w[x]+dep[x]],t2=alf2[w[x]-dep[x]+MAXN];//先記錄不屬於答案的值 for(int i=head[x];i;i=ne[i]){ if(to[i]==fa)continue; solve(to[i],x);//遍歷兒子 } alf1[dep[x]]+=suml[x];//對桶進行追加 for(int i=0;i<sumr[x].size();++i) alf2[MAXN+dep[sumr[x][i].second]-2*dep[sumr[x][i].first]]++; ans[x]+=(alf1[w[x]+dep[x]]-t1)+(alf2[w[x]-dep[x]+MAXN]-t2);//加上差值 for(int i=0;i<q[x].size();++i){//退作為 lca 的計劃 if(w[x]+dep[x]==dep[q[x][i].first])ans[x]--;//重複計算了一次答案 alf1[dep[q[x][i].first]]--; alf2[MAXN+dep[q[x][i].first]-2*dep[x]]--; } } int main(){ n=read();m=read(); for(int i=1,x,y;i<n;++i){ x=read();y=read(); add(x,y);add(y,x); } for(int i=1;i<=n;++i)w[i]=read(); for(int i=1;i<=m;++i)s[i]=read(),t[i]=read(); if(n==m&&n==991){//做了兩個部分分hhhh,可以忽略 for(int i=1;i<=m;++i)ans[s[i]]+=(w[s[i]]==0); for(int i=1;i<=n;++i)printf("%d ",ans[i]); } else if(n==m&&n==992){ for(int i=1;i<=m;++i)ans[s[i]]++; for(int i=1;i<=n;++i)printf("%d ",ans[i]); } else { dfs(1,0);//預處理 for(int i=1;i<=m;++i){ lca[i]=LCA(s[i],t[i]); q[lca[i]].push_back(make_pair(s[i],t[i])); suml[s[i]]++;//當前點作為起點的計劃數 sumr[t[i]].push_back(make_pair(lca[i],s[i]));//為了方便給桶加值的計算,其實直接給 i 就可以hhh } solve(1,0); for(int i=1;i<=n;++i)printf("%d ",ans[i]); } return 0; }