1. 程式人生 > 實用技巧 >LCA_樹上差分_洛谷_P1600天天愛跑步

LCA_樹上差分_洛谷_P1600天天愛跑步

題目:洛谷P1600 天天愛跑步

推薦一個寫的很好很好很好*n的題解

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
int const N=300000;
int f[2*N][40],deep[2*N],tot[2*N];
int lasta[2*N],t[2*N],dis[2*N],anss[2*N],lastl[2*N],lastt[2*N],l[2*N],r[2*N];
int b1[2*N],b2[2*N]; int n,m,numa,numl,numt,ans[2*N]; struct node{ int y,next; }a[2*N],lca[2*N],tn[2*N]; void adda(int x,int y) //儲存樹的邊 { numa++; a[numa].y=y; a[numa].next=lasta[x]; lasta[x]=numa; } void addt(int x,int y)//記錄以x為終點的玩家編號 { numt++; tn[numt].y=y; tn[numt].next
=lastt[x]; lastt[x]=numt; } void addl(int x,int y)//記錄起點和終點的lca為x的玩家編號 { numl++; lca[numl].y=y; lca[numl].next=lastl[x]; lastl[x]=numl; } void buildtree(int now)//lca預處理、記錄深度deep、找到每個點i的父節點f[i][0] { for(int i=lasta[now];i;i=a[i].next) if(!deep[a[i].y]) { f[a[i].y][
0]=now; deep[a[i].y]=deep[now]+1; buildtree(a[i].y); } } int getlca(int a,int b)//倍增求a和b的lca { if(deep[a]>deep[b]) swap(a,b); for(int i=29;i>=0;i--) if(deep[f[b][i]]>=deep[a]) b=f[b][i]; if(a==b) return a; for(int i=30;i>=0;i--) if(f[a][i]!=f[b][i]) { a=f[a][i]; b=f[b][i]; } return f[a][0]; } void dfs(int root)//深搜得到答案 { int t1=b1[deep[root]+t[root]]; //記錄最初上行的貢獻值b1 int t2=b2[t[root]-deep[root]+N]; //記錄最初下行的貢獻值b2 for(int i=lasta[root];i;i=a[i].next) //遍歷 if(a[i].y!=f[root][0]) dfs(a[i].y); b1[deep[root]]+=tot[root]; //加上自身作為起點,上行時對答案的貢獻 for(int i=lastt[root];i;i=tn[i].next) //加上自身作為終點,下行時對答案的貢獻 { b2[dis[tn[i].y]-deep[r[tn[i].y]]+N]++; } ans[root]+=b1[deep[root]+t[root]]-t1+b2[t[root]-deep[root]+N]-t2; //取差值得到以root為根的整顆子樹的貢獻 for(int i=lastl[root];i;i=lca[i].next) //將root作為lca的起點和終點的貢獻減去 { //對於一個點,在以它為根的子樹中,不經過它自身的路徑的貢獻與它無關 b1[deep[l[lca[i].y]]]--; b2[dis[lca[i].y]-deep[r[lca[i].y]]+N]--; } } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n-1;i++) { int x,y; scanf("%d%d",&x,&y); adda(x,y); adda(y,x); } f[1][0]=1; deep[1]=1; buildtree(1); for(int i=1;i<=30;i++) //lca初始化 for(int now=1;now<=n;now++) f[now][i]=f[f[now][i-1]][i-1]; for(int i=1;i<=n;i++) scanf("%d",&t[i]); for(int i=1;i<=m;i++) { scanf("%d%d",&l[i],&r[i]); int lca=getlca(l[i],r[i]); dis[i]=deep[r[i]]+deep[l[i]]-2*deep[lca]; tot[l[i]]++; //記錄以l[i]為起點的路徑的數量 addt(r[i],i); //將以r[i]為終點的路徑記錄下來 addl(lca,i); //將起點和終點以點lca為lca的路徑記錄下來 if(deep[lca]+t[lca]==deep[l[i]]) ans[lca]--; //如果lca為起點或者終點,上行和下行都會被記錄一次,所以要減去重複的一次 } dfs(1); for(int i=1;i<=n;i++) printf("%d ",ans[i]); }