树代码&题解整活
树
1、LCA
1.1 LCA模板
运用倍增思想,向根方向寻找,倍增跳跃。
#include <bits/stdc++.h> using namespace std; const int N = 5E5 + 5; int f[N][20], s, n, m, x, y, dep[N], b[N]; vector <int> g[N]; void dfs(int xx) { for(int i = 1; i <= 16; i++) { f[xx][i] = f[f[xx][i - 1]][i - 1]; } for(int i = 0; i < g[xx].size(); i++) { int temp = g[xx][i]; if(!b[temp]) { b[temp] = 1; dep[temp] = dep[xx] + 1; f[temp][0] = xx; dfs(temp); } } } int LCA(int x, int y) { if(dep[x] < dep[y]) { swap(x, y); } int temp = dep[x] - dep[y]; for(int i = 16; i >= 0; i--) { if((1 << i) & temp) { x = f[x][i]; } } if(x == y) { return x; } for(int i = 16; i >= 0; i--) { if(f[x][i] != f[y][i]) { x = f[x][i]; y = f[y][i]; } } return f[x][0]; } int main() { cin >> n >> m >> s; for(int i = 1; i < n; i++) { cin >> x >> y; g[x].push_back(y); g[y].push_back(x); } dep[s] = 1; b[s] = 1; dfs(s); while(m--) { cin >> x >> y; cout << LCA(x, y) << endl; } }
2、树上前缀和-博客
寻找树上任意两点的距离,做法之一为树上前缀和。
将两个点到根的距离之和相加即为答案。
2.1 模板
#include <iostream> #include <cstdlib> #include <cstdio> #include <algorithm> #include <cmath> #include <cstring> #include <queue> using namespace std; const int N = 200005; const int M = 25; int Q, n, m, head[N], dou[N][M], cnt, dep[N], b[N], k, total[N], wei[N]; struct node { int u, v, next, sum; } ed[N]; void add_edge(int u, int v, int k) { cnt++; ed[cnt].u = u; ed[cnt].v = v; ed[cnt].next = head[u]; ed[cnt].sum = k; head[u] = cnt; } void dfs(int xx) { for (int i = 1; i <= 20; i++) { dou[xx][i] = dou[dou[xx][i - 1]][i - 1]; } for (int i = head[xx]; i != 0; i = ed[i].next) { int temp = ed[i].v; if (b[temp] == 0) { b[temp] = 1; dep[temp] = dep[xx] + 1; total[temp] = ed[i].sum + total[xx]; dou[temp][0] = xx; dfs(temp); } } } int LCA(int x, int y) { if (dep[x] < dep[y]) swap(x, y); int temp = dep[x] - dep[y]; for (int i = 20; i >= 0; i--) { if (1 << i & temp) { x = dou[x][i]; } } if (x == y) return x; for (int i = 20; i >= 0; i--) { if (dou[x][i] != dou[y][i]) { x = dou[x][i]; y = dou[y][i]; } } return dou[x][0]; } int main() { cin >> n; cin >> m; for (int i = 1; i < n; i++) { int x, y, k; scanf("%d%d%d", &x, &y, &k); add_edge(x, y, k); add_edge(y, x, k); } b[1] = 1; dep[1] = 1; dfs(1); while (m--) { int x, y; scanf("%d%d", &x, &y); int lca = LCA(x, y); cout << total[x] + total[y] - total[lca] - total[lca] << endl; } return 0; }
2.2 JLOI2009]二叉树问题
上行边数$*2+$ 下行边数$*1$,可以把整个树的每一条边看成权值均为$1$ 的树,跑前缀和,左边到根的距离$*2+$ 右边到根的距离$*1$。
在求前缀和的过程中顺便把树的深度、宽度一把搞掉。
#include <iostream> #include <cstdlib> #include <cstdio> #include <algorithm> #include <cmath> #include <cstring> #include <queue> using namespace std; vector <int> mapp[105]; int n,anslen[105],len[105],maxlen,dep[105],b[105],maxdep,maxnlong=1,dou[10005][25]; void dfs(int x) { for(int i=1;i<=15;i++) { dou[x][i]=dou[dou[x][i-1]][i-1]; } for(int i=0;i<mapp[x].size();i++) { if(!b[mapp[x][i]]) { int temp=mapp[x][i]; dep[temp]=1+dep[x]; dou[temp][0]=x; b[mapp[x][i]]=1; len[temp]=dep[temp]; maxdep=max(dep[temp],maxdep); dfs(temp); } } } int LCA(int x,int y) { if(dep[x]<dep[y]) swap(x,y); int temp=dep[x]-dep[y]; for(int i=16;i>=0;i--) { if(1<<i&temp) { x=dou[x][i]; } } if(x==y) return x; for(int i=16;i>=0;i--) { if(dou[x][i]!=dou[y][i]) { x=dou[x][i]; y=dou[y][i]; } } return dou[x][0]; } int main() { cin>>n; for(int i=1;i<=n-1;i++) { int x,y; cin>>x>>y; mapp[x].push_back(y); mapp[y].push_back(x); } b[1]=1; dep[1]=1; maxdep=1; len[1]=1; dfs(1); int u,v; cin>>u>>v; int lca=LCA(u,v); cout<<maxdep<<endl; for(int i=1;i<=n;i++) { anslen[len[i]]+=1; } for(int i=1;i<=maxdep;i++) { maxlen=max(maxlen,anslen[i]); } cout<<maxlen<<endl; cout<<(dep[u]-dep[lca])*2+(dep[v]-dep[lca]); return 0; }
2.3 [AHOI2008]紧急集合 / 聚会
给定三个点,找到一个点,使得该点到题目所给的三个点的距离最小。
可以使用树上前缀和来写,需要一些常数上的优化,当然注意卡常
#include <iostream> #include <cstdlib> #include <cstdio> #include <algorithm> #include <cmath> #include <cstring> #include <queue> using namespace std; const int N=1000005; const int M=25; int dep[N],cnt,dou[N][M],n,m,x,y,z,b[N]; int head[N]; struct node{ int u,v,next; }ed[N]; inline int read() { int f=1; int xx=0; char c=getchar(); while(!isdigit(c)) { if(c=='-') f*=-1; c=getchar(); } while(isdigit(c)) { xx=xx*10+c-'0'; c=getchar(); } return xx*f; } void add_edge(int u,int v) { ed[++cnt].u=u; ed[cnt].v=v; ed[cnt].next=head[u]; head[u]=cnt; } void dfs(int xx) { for(int i=1;i<=16;i++) { dou[xx][i]=dou[dou[xx][i-1]][i-1]; } for(int i=head[xx];i!=0;i=ed[i].next) { if(b[ed[i].v]==0) { int temp=ed[i].v; b[temp]=1; dep[temp]=dep[xx]+1; dou[temp][0]=xx; dfs(temp); } } } int LCA(int x,int y) { if(dep[x]<dep[y]) swap(x,y); int temp=dep[x]-dep[y]; for(int i=16;i>=0;i--) { if(1<<i&temp) x=dou[x][i]; } if(x==y) return y; for(int i=16;i>=0;i--) { if(dou[x][i]!=dou[y][i]) { x=dou[x][i]; y=dou[y][i]; } } return dou[x][0]; } int main() { n=read(); m=read(); for(int i=1;i<n;i++) { x=read(); y=read(); add_edge(x,y); add_edge(y,x); } dep[1]=1; b[1]=1; dfs(1); while(m--) { x=read(); y=read(); z=read(); int xy=LCA(x,y); int yz=LCA(y,z); int xz=LCA(x,z); int ans=0,ansmoney=0; if(xy==yz) ans=xz; else if(xy==xz) ans=yz; else if(yz==xz) ans=xy; cout<<ans<<" "; ansmoney+=dep[x]+dep[y]-dep[xy]*2; ansmoney+=dep[y]+dep[z]-dep[yz]*2; ansmoney+=dep[x]+dep[z]-dep[xz]*2; ansmoney/=2; cout<<ansmoney<<endl; } return 0; }
3、树上差分
为前缀和的逆运算,所以与前缀和做法类同。
3.1 模板
#include <iostream> #include <cstdlib> #include <cstdio> #include <algorithm> #include <cmath> #include <cstring> #include <queue> using namespace std; const int N=100005; const int M=25; int cnt,head[N],dou[N][M],total[N],n,m,dep[N],b[N]; struct node{ int u,v,w,next; }ed[N]; void add_edge(int u,int v) { cnt++; ed[cnt].u=u; ed[cnt].v=v; ed[cnt].next=head[u]; head[u]=cnt; } void dfs(int xx) { for(int i=1;i<=20;i++) { dou[xx][i]=dou[dou[xx][i-1]][i-1]; } for(int i=head[xx];i;i=ed[i].next) { int temp=ed[i].v; if(!b[temp]) { b[temp]=1; dep[temp]=dep[xx]+1; dou[temp][0]=xx; dfs(temp); } } } int LCA(int x,int y) { if(dep[x]<dep[y]) { swap(x,y); } int temp=dep[x]-dep[y]; for(int i=20;i>=0;i--) { if((1<<i)&temp) { x=dou[x][i]; } } if(x==y) return x; for(int i=20;i>=0;i--) { if(dou[x][i]!=dou[y][i]) { x=dou[x][i]; y=dou[y][i]; } } return dou[x][0]; } void dfs2(int xx) { for(int i=head[xx];i;i=ed[i].next) { int temp=ed[i].v; if(!b[temp]) { b[temp]=1; dfs2(temp); total[xx]+=total[temp]; } } } int main() { cin>>n>>m; for(int i=1;i<n;i++) { int x,y; cin>>x>>y; add_edge(x,y); add_edge(y,x); } b[1]=1; dep[1]=1; dfs(1); memset(b,0,sizeof b); while(m--) { int xx,yy; cin>>xx>>yy; int lca=LCA(xx,yy); total[xx]++; total[yy]++; total[lca]--; total[dou[lca][0]]--; } b[1]=1; dfs2(1); int maxn=0; for(int i=1;i<=n;i++) { maxn=max(maxn,total[i]); } cout<<maxn; return 0; }
3.2 松鼠的新家
做法与差分模板类似。
#include <iostream> #include <cstdlib> #include <cstdio> #include <algorithm> #include <cmath> #include <cstring> #include <queue> using namespace std; const int N=1000005; const int M=25; int cnt,n,head[N],fa[N][M],dep[N],b[N],total[N],a[N],bt[N]; struct node{ int u,v,next; }ed[N]; void add_edge(int u,int v) { cnt++; ed[cnt].u=u; ed[cnt].v=v; ed[cnt].next=head[u]; head[u]=cnt; } void dfs(int xx) { for(int i=1;i<=20;i++) { fa[xx][i]=fa[fa[xx][i-1]][i-1]; } for(int i=head[xx];i;i=ed[i].next) { int temp=ed[i].v; if(!b[temp]) { b[temp]=1; dep[temp]=dep[xx]+1; fa[temp][0]=xx; dfs(temp); } } } void dfs2(int xx) { for(int i=head[xx];i;i=ed[i].next) { int temp=ed[i].v; if(!b[temp]) { b[temp]=1; dfs2(temp); total[xx]+=total[temp]; } } } int LCA(int x,int y) { if(dep[x]<dep[y]) { swap(x,y); } int temp=dep[x]-dep[y]; for(int i=20;i>=0;i--) { if(1<<i&temp) { x=fa[x][i]; } } if(x==y) { return x; } for(int i=20;i>=0;i--) { if(fa[x][i]!=fa[y][i]) { x=fa[x][i]; y=fa[y][i]; } } return fa[x][0]; } int main() { cin>>n; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1;i<n;i++) { int x,y; cin>>x>>y; add_edge(x,y); add_edge(y,x); } b[1]=1; dep[1]=1; dfs(1); memset(b,0,sizeof b); for(int i=2;i<n;i++) { int x=a[i-1]; int y=a[i]; if(!b[x]) { total[x]++; b[x]=1; } else { total[fa[x][0]]++; } if(!b[y]) { total[y]++; b[y]=1; } else { total[fa[y][0]]++; } int lca=LCA(x,y); total[lca]--; total[fa[lca][0]]--; } int x=a[n-1]; int y=a[n]; total[fa[x][0]]++; total[fa[y][0]]++; int lca=LCA(x,y); total[lca]--; total[fa[lca][0]]--; memset(b,0,sizeof b); b[1]=1; dfs2(1); for(int i=1;i<=n;i++) { cout<<total[i]<<endl; } return 0; }
4、数的直径
一些无向边且权值均为正整数构成的树上(可以无根),距离最长的2个点的距离为树的直径。