树链剖分[学习笔记]
树链剖分
壹.
树剖,就是树链剖分,将一棵树剖分成一堆链 (如说 \(\dots\) )
本文主要介绍重链剖分。
树剖成链之后一段重链上的 \(dfs\) 序是连续的,那么我们就可以对 \(dfs\) 序使用一些数据结构(树状数组、线段树等)
\(1\).一些变量及意义
- \(fa[x]\) \(x\) 的父节点
- \(depth[x]\) \(x\) 的深度
- \(siz[x]\) \(x\) 的子树大小,我们将根据他求重儿子
- \(wson[x]\) \(x\) 的重儿子,即 \(x\) 的儿子中子树最大的
- \(top[x]\) \(x\) 所在重链的链顶
- \(dfn[x]\) \(x\) 的 \(dfs\) 序,即第二次访问到 \(x\) 的时间
- \(id[i]\) 用来求 \(dfs\) 序为 \(i\) 的节点,即 \(id[dfn[x]]=x\)
- \(out[x]\) 第二次访问到 \(x\) 的时间
说白了,树剖就是用来求上述东西,然后就可以利用上述东西搞事情了。
\(2\).两遍 \(dfs\) 完成树剖
- 第一遍,我们可以轻松求出 \(fa[x]\),\(depth[x]\),\(siz[x]\),\(wson[x]\)。
void dfs(int x){ depth[x]=depth[fa[x]]+1; siz[x]=1; for(int i=head[x];i;i=e[i].next){ int y=e[i].to; if(y==fa[x]) continue; fa[y]=x; dfs(y); if(siz[y]>siz[wson[x]]) wson[x]=y;//更新重儿子 siz[x]+=siz[y]; } }
偷的图
- 第二遍,主要求 \(dfs\) 序,顺便求下 \(top\) 。我们优先跑重儿子,就可以保证重链上的 \(dfs\) 序连续。
void dfs(int x,int tp){ top[x]=tp; dfn[x]=++t;id[t]=x; if(wson[x]) dfs(wson[x],tp); for(int i=head[x];i;i=e[i].next){ int y=e[i].to; if(y==fa[x]||y==wson[x]) continue; dfs(y,y);//显然,轻儿子一定是重链的链顶 } out[x]=t; }
\(3\).求 \(LCA\)
我们从 \(u,v\) 分别上跳,跳到链顶,为了防止它们 像小H和小C一样 错过,所以我们每次选择链顶深度大的上跳,到一条链上后,深度小的即为 \(LCA\)。
int LCA(int u,int v){ while(top[u]!=top[v]){ if(depth[top[u]]<depth[top[v]]) swap(u,v); u=fa[top[u]]; } return (depth[u]<depth[v] ? u : v); }
跳重链的复杂度为 \(O(\log _2n)\) 但是它严格跑不满,所以它的速度薄纱倍增和 \(tarjan\) 。
\(4\).一些性质和利用
- 树剖成链之后一段重链上的 \(dfs\) 序是连续的,用线段树等即可优秀地维护重链信息
- 从任意节点跳到根节点最多跳 \(\log _2n\) 次,多数情况下跑不满。
之前总听人说树剖码量大,学了之后才发现树剖就那么几行,不太会变,真正码量大的是维护重链信息的数据结构——比如线段树。如果你对线段树很怵头,先学好线段树吧,或者说,打完树剖你也就不怵头了。
贰.\(hs\)题单
\(T_A\) ZJOI2008 树的统计
板子,树剖,线段树维护单点修改,最大值和区间和,跳重链求解即可。
注意此题值域 \([-30000,30000]\) ,求最大值时注意初值赋极小
$CODE$
#include<bits/stdc++.h> using namespace std; #define swap(x,y) (x^=y,y^=x,x^=y) #define read read() #define pt puts("") inline int read{ int x=0,f=1;char c=getchar(); while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();} while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar(); return f*x; } void write(int x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); return; } const int N=3*1e4+10; #define INF 30010 int n; int w[N]; struct EDGE{int next,to;}e[N<<1]; int head[N],total; void add(int u,int v){e[++total]={head[u],v};head[u]=total;} namespace Tree_Chain_Partition{ int fa[N],depth[N],wson[N],siz[N]; int top[N],dfn[N],id[N],out[N],t; void dfs1(int x){ depth[x]=depth[fa[x]]+1; siz[x]=1; for(int i=head[x];i;i=e[i].next){ int y=e[i].to; if(y==fa[x]) continue; fa[y]=x; dfs1(y); if(siz[y]>siz[wson[x]]) wson[x]=y; siz[x]+=siz[y]; } } void dfs2(int x,int tp){ top[x]=tp; dfn[x]=++t;id[t]=x; if(wson[x]) dfs2(wson[x],tp); for(int i=head[x];i;i=e[i].next){ int y=e[i].to; if(y==fa[x]||y==wson[x]) continue; dfs2(y,y); } out[x]=t; } int LCA(int u,int v){ while(top[u]!=top[v]){ if(depth[top[u]]<depth[top[v]]) swap(u,v); u=fa[top[u]]; } return (depth[u]<depth[v]?u:v); } } using namespace Tree_Chain_Partition; namespace Segment_Tree{ struct Tree{ int sum,mx,l,r; #define l(i) tr[i].l #define r(i) tr[i].r #define sum(i) tr[i].sum #define mx(i) tr[i].mx #define ls(i) (i<<1) #define rs(i) (i<<1|1) }tr[N<<2]; void pushup(int i){ sum(i)=sum(ls(i))+sum(rs(i)); mx(i)=max(mx(ls(i)),mx(rs(i))); } void build(int i,int l,int r){ l(i)=l;r(i)=r; if(l==r){ mx(i)=sum(i)=w[id[l]]; return; } int mid=(l+r)>>1; build(ls(i),l,mid);build(rs(i),mid+1,r); pushup(i); return; } void modify(int i,int x,int k){ if(l(i)==r(i)){ sum(i)=mx(i)=k; return; } int mid=(l(i)+r(i))>>1; if(x<=mid) modify(ls(i),x,k); else modify(rs(i),x,k); pushup(i); return; } int asksum(int i,int ql,int qr){ int l=l(i),r=r(i); if(ql<=l&&r<=qr) return sum(i); int mid=(l+r)>>1,res=0;; if(ql<=mid) res+=asksum(ls(i),ql,qr); if(mid<qr) res+=asksum(rs(i),ql,qr); return res; } int askmax(int i,int ql,int qr){ int l=l(i),r=r(i); if(ql<=l&&r<=qr) return mx(i); int mid=(l+r)>>1,res=-INF; if(ql<=mid) res=max(res,askmax(ls(i),ql,qr)); if(mid<qr) res=max(res,askmax(rs(i),ql,qr)); return res; } }using namespace Segment_Tree; int ans; int qsum(int u,int lca){ int res=0; while(depth[top[u]]>depth[lca]&&top[u]!=top[lca]){ res+=asksum(1,dfn[top[u]],dfn[u]); u=fa[top[u]]; } res+=(u==lca?w[u]:asksum(1,dfn[lca],dfn[u])); return res; } int qmax(int u,int lca){ int res=-INF; while(depth[top[u]]>depth[lca]&&top[u]!=top[lca]){ res=max(res,askmax(1,dfn[top[u]],dfn[u])); u=fa[top[u]]; } res=max(res,(u==lca?w[u]:askmax(1,dfn[lca],dfn[u]))); return res; } signed main() { #ifndef ONLINE_JUDGE freopen("lty.in","r",stdin); freopen("lty.out","w",stdout); #endif n=read; for(int i=1,a,b;i<n;i++) a=read,b=read,add(a,b),add(b,a); for(int i=1;i<=n;i++) w[i]=read; dfs1(1);dfs2(1,1); build(1,1,t); int T;T=read; char op[7];int u,v,k; while(T-->0){ scanf("%s",op);u=read; if(op[0]=='C'){ k=read;w[u]=k; modify(1,dfn[u],k); } else{ v=read; int lca=LCA(u,v); if(op[1]=='S'){ ans=-w[lca]; ans+=qsum(u,lca); ans+=qsum(v,lca); } else{ ans=-INF; ans=max(ans,qmax(u,lca)); ans=max(ans,qmax(v,lca)); } write(ans),pt; } } return 0; }
\(T_B\) HAOI2015 树上操作
板子,树剖,线段树维护区间修改区间求和。
$CODE$
#include<bits/stdc++.h> using namespace std; #define int long long #define read read() #define pt puts("") inline int read{ int x=0,f=1;char c=getchar(); while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();} while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar(); return f*x; } void write(int x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); return; } #define N 100010 int n,w[N]; int m; namespace Tree_Chain_Partition{ struct EDGE{int next,to;}e[N<<1]; int head[N],total; void add(int u,int v){e[++total]={head[u],v};head[u]=total;} int fa[N],wson[N],depth[N],siz[N]; int top[N],dfn[N],out[N],id[N],t; void dfs1(int x){ depth[x]=depth[fa[x]]+1; siz[x]=1; for(int i=head[x];i;i=e[i].next){ int y=e[i].to; if(y==fa[x]) continue; fa[y]=x; dfs1(y); if(siz[y]>siz[wson[x]]) wson[x]=y; siz[x]+=siz[y]; } } void dfs2(int x,int tp){ top[x]=tp; dfn[x]=++t;id[t]=x; if(wson[x]) dfs2(wson[x],tp); for(int i=head[x];i;i=e[i].next){ int y=e[i].to; if(y==fa[x]||y==wson[x]) continue; dfs2(y,y); } out[x]=t; } } using namespace Tree_Chain_Partition; namespace Segment_Tree{ struct Tree{ int l,r,sum,lazy; #define l(i) tr[i].l #define r(i) tr[i].r #define sum(i) tr[i].sum #define lazy(i) tr[i].lazy #define ls(i) (i<<1) #define rs(i) (i<<1|1) }tr[N<<2]; void pushup(int i){sum(i)=sum(ls(i))+sum(rs(i));} void pushdown(int i){ if(!lazy(i)) return; sum(ls(i))+=(r(ls(i))-l(ls(i))+1)*lazy(i); sum(rs(i))+=(r(rs(i))-l(rs(i))+1)*lazy(i); lazy(ls(i))+=lazy(i); lazy(rs(i))+=lazy(i); lazy(i)=0; return; } void build(int i,int l,int r){ l(i)=l,r(i)=r; if(l==r){ sum(i)=w[id[l]]; return; } int mid=(l+r)>>1; build(ls(i),l,mid);build(rs(i),mid+1,r); pushup(i); return; } void modify(int i,int x,int k){ int l=l(i),r=r(i); if(l==r){ sum(i)+=k; return; } pushdown(i); int mid=(l+r)>>1; if(x<=mid) modify(ls(i),x,k); else modify(rs(i),x,k); pushup(i); return; } void update(int i,int ml,int mr,int k){ int l=l(i),r=r(i); if(ml<=l&&r<=mr){ lazy(i)+=k; sum(i)+=(r(i)-l(i)+1)*k; return; } pushdown(i); int mid=(l+r)>>1; if(ml<=mid) update(ls(i),ml,mr,k); if(mid<mr) update(rs(i),ml,mr,k); pushup(i); return; } int query(int i,int ql,int qr){ int l=l(i),r=r(i); if(ql<=l&&r<=qr) return sum(i); pushdown(i); int mid=(l+r)>>1,res=0; if(ql<=mid) res+=query(ls(i),ql,qr); if(mid<qr) res+=query(rs(i),ql,qr); pushup(i); return res; } } using namespace Segment_Tree; signed main() { #ifndef ONLINE_JUDGE freopen("lty.in","r",stdin); freopen("lty.out","w",stdout); #endif n=read;m=read; for(int i=1;i<=n;i++) w[i]=read; for(int i=1,a,b;i<n;i++) a=read,b=read,add(a,b),add(b,a); dfs1(1);dfs2(1,1); build(1,1,t); int op,u,v,k,ans; while(m-->0){ op=read;u=read; switch(op){ case 1: k=read; modify(1,dfn[u],k); break; case 2: k=read; update(1,dfn[u],out[u],k); break; case 3: ans=0; while(u){ ans+=query(1,dfn[top[u]],dfn[u]); u=fa[top[u]]; } write(ans);pt; break; default:break; } } return 0; }
\(T_C\) 难存的情缘
vjudge上没找着,挂学校OJ吧
板子,树剖,将边权下放到点上,然后单点修改区间查询最大值即可。
$CODE$
#include<bits/stdc++.h> using namespace std; #define read read() #define pt puts("") inline int read{ int x=0,f=1;char c=getchar(); while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();} while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar(); return f*x; } void write(int x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); return; } #define N 10010 int n,w[N]; namespace Tree_Chain_Partition{ struct EDGE{int next,to,ww,number;}e[N<<1]; int head[N],total;int dot[N]; void add(int u,int v,int ww,int id){e[++total]={head[u],v,ww,id};head[u]=total;} int fa[N],depth[N],wson[N],siz[N]; int top[N],dfn[N],id[N],t; void dfs1(int x){ depth[x]=depth[fa[x]]+1; siz[x]=1; for(int i=head[x];i;i=e[i].next){ int y=e[i].to; if(y==fa[x]) continue; fa[y]=x; // cerr<<x<<"->"<<y<<'\n'; // cerr<<"e["<<i<<"].num="<<e[i].number<<'\n'; // cerr<<"e["<<i<<"].ww="<<e[i].ww<<'\n'; dot[e[i].number]=y; w[y]=e[i].ww; dfs1(y); if(siz[y]>siz[wson[x]]) wson[x]=y; siz[x]+=siz[y]; } } void dfs2(int x,int tp){ top[x]=tp; dfn[x]=++t;id[t]=x; if(wson[x]) dfs2(wson[x],tp); for(int i=head[x];i;i=e[i].next){ int y=e[i].to; if(y==fa[x]||y==wson[x]) continue; dfs2(y,y); } } } using namespace Tree_Chain_Partition; namespace Segment_Tree{ struct Tree{ int l,r,mx; #define ls(i) (i<<1) #define rs(i) (i<<1|1) #define l(i) tr[i].l #define r(i) tr[i].r #define mx(i) tr[i].mx }tr[N<<2]; void pushup(int i){mx(i)=max(mx(ls(i)),mx(rs(i)));} void build(int i,int l,int r){ l(i)=l,r(i)=r; if(l==r){ mx(i)=w[id[l]]; return; } int mid=(l+r)>>1; build(ls(i),l,mid);build(rs(i),mid+1,r); pushup(i); return; } void modify(int i,int x,int k){ int l=l(i),r=r(i); if(l==r){ mx(i)=k; return; } int mid=(l+r)>>1; if(x<=mid) modify(ls(i),x,k); else modify(rs(i),x,k); pushup(i); return; } int query(int i,int ql,int qr){ int l=l(i),r=r(i); if(ql<=l&&r<=qr){ return mx(i); } int mid=(l+r)>>1,res=0; if(ql<=mid) res=max(res,query(ls(i),ql,qr)); if(mid<qr) res=max(res,query(rs(i),ql,qr)); return res; } } using namespace Segment_Tree; signed main() { #ifndef ONLINE_JUDGE freopen("lty.in","r",stdin); freopen("lty.out","w",stdout); #endif n=read; for(int a,b,c,i=1;i<n;i++){ a=read,b=read,c=read; add(a,b,c,i);add(b,a,c,i); } dfs1(1);dfs2(1,1); build(1,1,t); char op[7]; int u,v,k,i,ans; while(1){ scanf("%s",op); switch(op[0]){ case 'C': i=read,k=read; u=dot[i]; modify(1,dfn[u],k); break; case 'Q': u=read,v=read;ans=0; while(top[u]!=top[v]){ if(depth[top[u]]<depth[top[v]]) swap(u,v); ans=max(ans,query(1,dfn[top[u]],dfn[u])); u=fa[top[u]]; } ans=max(ans,(depth[u]<depth[v]?query(1,dfn[wson[u]],dfn[v]):query(1,dfn[wson[v]],dfn[u]))); write(ans);pt; break; default: return 0; } } return 0; }
\(T_D\) 货车运输
打 \(LCA\) 时打过一次,一样的思路,把倍增换成树剖。
首先一个 \(kruskal\) 重构树,然后就很板了,树剖,线段树维护区间最小值。
$CODE$
#include<bits/stdc++.h> using namespace std; #define swap(x,y) (x^=y,y^=x,x^=y) #define read read() #define pt puts("") inline int read{ int x=0,f=1;char c=getchar(); while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();} while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar(); return f*x; } void write(int x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); return; } #define N 20010 #define M 50010 int n,m; int w[N<<1]; namespace klskr{ struct EDGE{int next,from,to,w;}e[M<<1],q[M<<1]; int head[N],total,hq[N],tot; void add(int u,int v,int w){e[++total]={head[u],u,v,w};head[u]=total;} void addq(int u,int v){ tot++; q[tot].next=hq[u]; q[tot].to=v; hq[u]=tot; } int rt[N]; int find(int x){return rt[x]==x?x:(rt[x]=find(rt[x]));} } using namespace klskr; namespace Tree_Chain_Partition{ int fa[N],siz[N],depth[N],wson[N]; int top[N],dfn[N],id[N],t; void dfs1(int x){ depth[x]=depth[fa[x]]+1; siz[x]=1; for(int i=hq[x];i;i=q[i].next){ int y=q[i].to; if(y==fa[x]) continue; fa[y]=x; dfs1(y); if(siz[y]>siz[wson[x]]) wson[x]=y; siz[x]+=siz[y]; } } void dfs2(int x,int tp){ top[x]=tp; dfn[x]=++t;id[t]=x; if(wson[x]) dfs2(wson[x],tp); for(int i=hq[x];i;i=q[i].next){ int y=q[i].to; if(y==wson[x]||y==fa[x]) continue; dfs2(y,y); } } } using namespace Tree_Chain_Partition; namespace Segment_Tree{ struct Tree{ int l,r,minn; #define l(i) tr[i].l #define r(i) tr[i].r #define ls(i) (i<<1) #define rs(i) (i<<1|1) #define minn(i) tr[i].minn }tr[N<<2]; void build(int i,int l,int r){ l(i)=l,r(i)=r; if(l==r){ minn(i)=w[id[l]]; return; } int mid=(l+r)>>1; build(ls(i),l,mid); build(rs(i),mid+1,r); minn(i)=min(minn(ls(i)),minn(rs(i))); return; } int query(int i,int ql,int qr){ int l=l(i),r=r(i); if(ql<=l&&r<=qr) return minn(i); int mid=(l+r)>>1,res=1e8; if(ql<=mid) res=min(res,query(ls(i),ql,qr)); if(mid<qr) res=min(res,query(rs(i),ql,qr)); return res; } } using namespace Segment_Tree; signed main() { #ifndef ONLINE_JUDGE freopen("lty.in","r",stdin); freopen("lty.out","w",stdout); #endif n=read,m=read; for(int i=1;i<=(n<<1);i++) rt[i]=i,w[i]=1e8; for(int i=1,a,b,c;i<=m;i++) a=read,b=read,c=read,add(a,b,c),add(b,a,c); sort(e+1,e+total+1,[](EDGE a,EDGE b){return a.w>b.w;}); for(int i=1;i<=total;i++){ EDGE x=e[i]; int u=x.from,v=x.to; int uu=find(u),vv=find(v); if(uu!=vv){ rt[uu]=rt[vv]=++n; addq(n,uu);addq(uu,n); addq(n,vv),addq(vv,n); w[n]=x.w; } } for(int i=n;i>=1;i--){ if(!dfn[i]){ dfs1(i); dfs2(i,i); } } build(1,1,t); int T,u,v,ans; T=read; while(T-->0){ u=read,v=read; int uu=find(u),vv=find(v); if(uu!=vv){ puts("-1"); continue; } ans=1e8; while(top[u]!=top[v]){ if(depth[top[u]]<depth[top[v]]) swap(u,v); ans=min(ans,query(1,dfn[top[u]],dfn[u])); u=fa[top[u]]; } ans=min(ans,(depth[u]<depth[v] ? query(1,dfn[u],dfn[v]) : query(1,dfn[v],dfn[u]))); write(ans);pt; } return 0; }
\(T_E\) 遥远的国度
涉及换根,那就换,我们"假装"换,(用一个变量 \(rt\) 存储当前的根,直接换),但实际根仍为 \(1\),树剖,线段树维护单点修改和区间最小值,单点修改不变,对于区间最小值:
- \(rt=u\) 查整棵树。
- \(rt\) 不在 \(u\) 子树里,该查啥查啥。
- \(rt\) 在 \(u\) 子树里,考虑将查询区间分成两段,设 \(v\) 为 \(u\) 在 \(rt\) 方向上的儿子,答案显然是总答案减去去 \(v\) 的子树的贡献,即分成 \([1,dfn[v]-1]\) 和 \([out[v]+1,t]\) 两段答案进行统计即可。
对于换根我们的大体思路就是这样。
$CODE$
#include<bits/stdc++.h> using namespace std; #define swap(x,y) (x^=y,y^=x,x^=y) #define int long long #define read read() #define pt puts("") inline int read{ int x=0,f=1;char c=getchar(); while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();} while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar(); return f*x; } void write(int x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); return; } #define N 100010 const int MAX=1e10; int n,m,rt; int w[N]; struct EDGE{int next,to;}e[N<<1]; int head[N],total; void add(int u,int v){ e[++total]={head[u],v}; head[u]=total;} namespace Tree_Chain_Partition{ int fa[N],siz[N],wson[N],depth[N]; int dfn[N],out[N],id[N],t,top[N]; void dfs(int x){ depth[x]=depth[fa[x]]+1; siz[x]=1; for(int i=head[x];i;i=e[i].next){ int y=e[i].to; if(y==fa[x]) continue; fa[y]=x; dfs(y); if(siz[y]>siz[wson[x]]) wson[x]=y; siz[x]+=siz[y]; } } void dfs(int x,int tp){ top[x]=tp; dfn[x]=++t;id[t]=x; if(wson[x]) dfs(wson[x],tp); for(int i=head[x];i;i=e[i].next){ int y=e[i].to; if(y==fa[x]||y==wson[x]) continue; dfs(y,y); } out[x]=t; } } using namespace Tree_Chain_Partition; namespace Segment_Tree{ struct Tree{ int l,r,minn,lazy; #define l(i) tr[i].l #define r(i) tr[i].r #define minn(i) tr[i].minn #define lazy(i) tr[i].lazy #define ls(i) (i<<1) #define rs(i) (i<<1|1) }tr[N<<2]; void pushup(int i){minn(i)=min(minn(ls(i)),minn(rs(i)));} void pushdown(int i){ if(!lazy(i)) return; minn(ls(i))=minn(rs(i))=lazy(ls(i))=lazy(rs(i))=lazy(i); lazy(i)=0; return; } void build(int i,int l,int r){ l(i)=l,r(i)=r; if(l==r){ minn(i)=w[id[l]]; return; } int mid=(l+r)>>1; build(ls(i),l,mid); build(rs(i),mid+1,r); pushup(i); return; } void update(int i,int ul,int ur,int k){ int l=l(i),r=r(i); if(ul<=l&&r<=ur){ minn(i)=lazy(i)=k; return; } pushdown(i); int mid=(l+r)>>1; if(ul<=mid) update(ls(i),ul,ur,k); if(mid<ur) update(rs(i),ul,ur,k); pushup(i); return; } int query(int i,int ql,int qr){ int l=l(i),r=r(i); if(ql<=l&&r<=qr){ return minn(i); } pushdown(i); int mid=(l+r)>>1,res=MAX; if(ql<=mid) res=min(res,query(ls(i),ql,qr)); if(mid<qr) res=min(res,query(rs(i),ql,qr)); pushup(i); return res; } } using namespace Segment_Tree; signed main() { #ifndef ONLINE_JUDGE freopen("lty.in","r",stdin); freopen("lty.out","w",stdout); #endif n=read,m=read; for(int a,b,i=1;i<n;i++){ a=read,b=read; add(a,b),add(b,a); } for(int i=1;i<=n;i++) w[i]=read; dfs(1);dfs(1,1); build(1,1,t); rt=read; int op,u,v,k,ans; while(m-->0){ op=read; switch(op){ case 1: rt=read; break; case 2: u=read,v=read,k=read; while(top[u]!=top[v]){ if(depth[top[u]]<depth[top[v]]) swap(u,v); update(1,dfn[top[u]],dfn[u],k); u=fa[top[u]]; } depth[u]<depth[v] ? update(1,dfn[u],dfn[v],k) : update(1,dfn[v],dfn[u],k); break; case 3: u=read;ans=MAX; if(u==rt) ans=minn(1); else if(dfn[u]<dfn[rt]&&dfn[rt]<=out[u]){ v=rt; while(top[u]!=top[v]){ if(fa[top[v]]==u){ v=top[v];break; } v=fa[top[v]]; } if(fa[v]!=u) v=wson[u]; if(dfn[v]-1>=1) ans=min(ans,query(1,1,dfn[v]-1)); if(out[v]+1<=t) ans=min(ans,query(1,out[v]+1,t)); } else ans=query(1,dfn[u],out[u]); write(ans);pt; break; default: break; } } return 0; }
对于换根,还可以求 \(LCA\),比如 Jamie and Tree,我们可以分讨归纳证明,节点 \(u,v\) 在根为 \(rt\) 时的 \(LCA\) 为 \(lca(u,v),lca(u,rt),lca(v,rt)\) 中深度最大的节点,其他的按照上面的思路维护即可,注意修改和查询都要分讨。
$CODE$
#include<bits/stdc++.h> using namespace std; #define int long long #define read read() #define pt puts("") inline int read{ int x=0,f=1;char c=getchar(); while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();} while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar(); return f*x; } void write(int x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); return; } const int N=1e5+10; int n,q; int w[N]; struct EDGE{int next,to;}e[N<<1]; int head[N],total,rt=1; void add(int u,int v){e[++total]={head[u],v};head[u]=total;} namespace Tree_Chain_Partition{ int fa[N],depth[N],siz[N],wson[N]; int top[N],dfn[N],out[N],id[N],t; void dfs(int x){ depth[x]=depth[fa[x]]+1; siz[x]=1; for(int i=head[x];i;i=e[i].next){ int y=e[i].to; if(y==fa[x]) continue; fa[y]=x; dfs(y); if(siz[y]>siz[wson[x]]) wson[x]=y; siz[x]+=siz[y]; } } void dfs(int x,int tp){ top[x]=tp; dfn[x]=++t;id[t]=x; if(wson[x]) dfs(wson[x],tp); for(int i=head[x];i;i=e[i].next){ int y=e[i].to; if(y==fa[x]||y==wson[x]) continue; dfs(y,y); } out[x]=t; } int LCA(int u,int v){ while(top[u]!=top[v]){ if(depth[top[u]]<depth[top[v]]) swap(u,v); u=fa[top[u]]; } return (depth[u]<depth[v] ? u : v); } int findson(int u,int rt){//查询u在rt方向上的儿子 int v=rt; while(top[v]!=top[u]){ if(fa[top[v]]==u){ v=top[v];break; } v=fa[top[v]]; } if(fa[v]!=u) v=wson[u]; return v; } } using namespace Tree_Chain_Partition; namespace Segment_Tree{ struct Tree{ int l,r,sum,lazy; #define l(i) tr[i].l #define r(i) tr[i].r #define sum(i) tr[i].sum #define lazy(i) tr[i].lazy #define ls(i) (i<<1) #define rs(i) (i<<1|1) }tr[N<<2]; void pushup(int i){sum(i)=sum(ls(i))+sum(rs(i));} void pushdown(int i){ if(!lazy(i)) return; sum(ls(i))+=(r(ls(i))-l(ls(i))+1)*lazy(i); sum(rs(i))+=(r(rs(i))-l(rs(i))+1)*lazy(i); lazy(ls(i))+=lazy(i),lazy(rs(i))+=lazy(i); lazy(i)=0; return; } void build(int i,int l,int r){ l(i)=l,r(i)=r; if(l==r){ sum(i)=w[id[l]]; return; } int mid=(l+r)>>1; build(ls(i),l,mid); build(rs(i),mid+1,r); pushup(i); return; } void update(int i,int ul,int ur,int k){ int l=l(i),r=r(i); if(ul<=l&&r<=ur){ lazy(i)+=k; sum(i)+=(r-l+1)*k; return; } pushdown(i); int mid=(l+r)>>1; if(ul<=mid) update(ls(i),ul,ur,k); if(mid<ur) update(rs(i),ul,ur,k); pushup(i); return; } int query(int i,int ql,int qr){ int l=l(i),r=r(i); if(ql<=l&&r<=qr){ return sum(i); } pushdown(i); int mid=(l+r)>>1,res=0; if(ql<=mid) res+=query(ls(i),ql,qr); if(mid<qr) res+=query(rs(i),ql,qr); pushup(i); return res; } } using namespace Segment_Tree; signed main() { #ifndef ONLINE_JUDGE freopen("lty.in","r",stdin); freopen("lty.out","w",stdout); #endif n=read,q=read; for(int i=1;i<=n;i++) w[i]=read; for(int a,b,i=1;i<n;i++){ a=read,b=read;add(a,b),add(b,a); } dfs(1);dfs(1,1); build(1,1,t); int op,u,v,k,ans; int lca,lca1,lca2,lca3; while(q-->0){ op=read; switch(op){ case 1: rt=read; break; case 2: u=read,v=read,k=read; lca1=LCA(u,v); lca2=LCA(u,rt); lca3=LCA(v,rt); lca=(depth[lca1]>depth[lca2] ? lca1 : lca2); lca=(depth[lca]>depth[lca3] ? lca : lca3); if(lca==rt) update(1,1,t,k); else if(dfn[lca]<dfn[rt]&&dfn[rt]<=out[lca]){ v=findson(lca,rt); if(dfn[v]-1>=1) update(1,1,dfn[v]-1,k); if(out[v]+1<=t) update(1,out[v]+1,t,k); } else update(1,dfn[lca],out[lca],k); break; case 3: u=read; ans=0; if(u==rt){ ans=query(1,1,t); } else if(dfn[u]<dfn[rt]&&dfn[rt]<=out[u]){ v=findson(u,rt); if(dfn[v]-1>=1) ans+=query(1,1,dfn[v]-1); if(out[v]+1<=t) ans+=query(1,out[v]+1,t); } else ans=query(1,dfn[u],out[u]); write(ans),pt; break; default:break; } } return 0; }
\(T_F\) 染色
思路差不多,多了点细节,线段树每个节点多维护一个 \(lc\) 和一个 \(rc\) 为每段左右端点的颜色,然后注意查完一段 \([dfn[top[u]],dfn[u]]\) 后,特判树链交界处,查一下 \(top[u]\) 和 \(fa[top[u]]\) 的颜色是否相同,相同就要让答案减1。
我纯唐,调了一下午分块,觉得分块好维护,寄。
$CODE$
#include<bits/stdc++.h> using namespace std; #define swap(x,y) (x^=y,y^=x,x^=y) #define read read() #define pt puts("") inline int read{ int x=0,f=1;char c=getchar(); while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();} while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar(); return f*x; } void write(int x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); return; } const int N=1e5+10; #define M 400 int n,m; int c[N]; struct EDGE{int next,to;}e[N<<1]; int head[N],total; void add(int u,int v){e[++total]={head[u],v};head[u]=total;} namespace Tree_Chain_Partition{ int fa[N],siz[N],depth[N],wson[N]; int top[N],dfn[N],id[N],t; void dfs(int x){ depth[x]=depth[fa[x]]+1; siz[x]=1; for(int i=head[x];i;i=e[i].next){ int y=e[i].to; if(y==fa[x]) continue; fa[y]=x; dfs(y); if(siz[y]>siz[wson[x]]) wson[x]=y; siz[x]+=siz[y]; } } void dfs(int x,int tp){ top[x]=tp; dfn[x]=++t;id[t]=x; if(wson[x]) dfs(wson[x],tp); for(int i=head[x];i;i=e[i].next){ int y=e[i].to; if(y==fa[x]||y==wson[x]) continue; dfs(y,y); } } } using namespace Tree_Chain_Partition; namespace Segment_Tree{ struct Tree{ int l,r,cnt,lc,rc,lazy; #define l(i) tr[i].l #define r(i) tr[i].r #define cnt(i) tr[i].cnt #define lc(i) tr[i].lc #define rc(i) tr[i].rc #define lazy(i) tr[i].lazy #define ls(i) (i<<1) #define rs(i) (i<<1|1) }tr[N<<2]; void pushup(int i){ lc(i)=lc(ls(i)),rc(i)=rc(rs(i)); cnt(i)=cnt(ls(i))+cnt(rs(i)); if(rc(ls(i))==lc(rs(i))) --cnt(i); return; } void pushdown(int i){ if(!lazy(i)) return; lazy(ls(i))=lazy(rs(i))=lazy(i); lc(ls(i))=rc(ls(i))=lazy(i); lc(rs(i))=rc(rs(i))=lazy(i); cnt(ls(i))=cnt(rs(i))=1; lazy(i)=0; return; } void build(int i,int l,int r){ l(i)=l,r(i)=r; if(l==r){ lc(i)=rc(i)=c[id[l]]; cnt(i)=1; return; } int mid=(l+r)>>1; build(ls(i),l,mid); build(rs(i),mid+1,r); pushup(i); return; } void update(int i,int ul,int ur,int k){ int l=l(i),r=r(i); if(ul<=l&&r<=ur){ lazy(i)=k; lc(i)=rc(i)=k; cnt(i)=1; return; } pushdown(i); int mid=(l+r)>>1; if(ul<=mid) update(ls(i),ul,ur,k); if(mid<ur) update(rs(i),ul,ur,k); pushup(i); return; } int ask(int i,int x){ int l=l(i),r=r(i); if(l==r) return lc(i); pushdown(i); int mid=(l+r)>>1,res; if(x<=mid) res=ask(ls(i),x); else res=ask(rs(i),x); pushup(i); return res; } int query(int i,int ql,int qr){ int l=l(i),r=r(i); if(ql<=l&&r<=qr){ return cnt(i); } pushdown(i); int mid=(l+r)>>1,res=0; if(ql<=mid&&mid<qr){ if(rc(ls(i))==lc(rs(i))) res--; } if(ql<=mid) res+=query(ls(i),ql,qr); if(mid<qr) res+=query(rs(i),ql,qr); pushup(i); return res; } } using namespace Segment_Tree; signed main() { #ifndef ONLINE_JUDGE freopen("lty.in","r",stdin); freopen("lty.out","w",stdout); #endif n=read,m=read; for(int i=1;i<=n;i++) c[i]=read; for(int a,b,i=1;i<n;i++){ a=read,b=read;add(a,b),add(b,a); } dfs(1),dfs(1,1); build(1,1,t); char op;int u,v,k,ans; while(m-->0){ op=getchar();while(op!='Q'&&op!='C') op=getchar(); u=read,v=read; if(op=='Q'){ ans=0; while(top[u]!=top[v]){ if(depth[top[u]]<depth[top[v]]) swap(u,v); ans+=query(1,dfn[top[u]],dfn[u]); u=top[u]; int a=ask(1,dfn[u]),b=ask(1,dfn[fa[u]]); if(a==b) ans--; u=fa[u]; } ans+=(depth[u]<depth[v] ? query(1,dfn[u],dfn[v]) : query(1,dfn[v],dfn[u])); write(ans);pt; } if(op=='C'){ k=read; while(top[u]!=top[v]){ if(depth[top[u]]<depth[top[v]]) swap(u,v); update(1,dfn[top[u]],dfn[u],k); u=fa[top[u]]; } depth[u]<depth[v] ? update(1,dfn[u],dfn[v],k) : update(1,dfn[v],dfn[u],k); } } return 0; }
\(T_G\) 运输计划
二分答案树上差分,几个月前打 \(LCA\) 时就打了,当时倍增就被卡了,树剖跑飞快。
$CODE$
#include<bits/stdc++.h> using namespace std; #define swap(x,y) (x^=y,y^=x,x^=y) #define read read() #define pt puts("") inline int read{ int x=0,f=1;char c=getchar(); while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();} while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar(); return f*x; } void write(int x){ if(x<0) putchar('-'),x=-x; if(x>9) write(x/10); putchar(x%10+'0'); return; } #define N 300010 int n,m; int MAX; struct EDGE{int next,to,t;}e[N<<1]; int head[N],total; void add(int u,int v,int t){e[++total]={head[u],v,t};head[u]=total;} int edge[N]; namespace Tree_Chain_Partition{ int fa[N],siz[N],wson[N],depth[N]; int top[N]; int dis[N]; void dfs1(int x){ depth[x]=depth[fa[x]]+1; siz[x]=1; for(int i=head[x];i;i=e[i].next){ int y=e[i].to; if(y==fa[x]) continue; edge[y]=i; dis[y]=dis[x]+e[i].t; fa[y]=x; dfs1(y); if(siz[y]>siz[wson[x]]) wson[x]=y; siz[x]+=siz[y]; } } void dfs2(int x,int tp){ top[x]=tp; if(wson[x]) dfs2(wson[x],tp); for(int i=head[x];i;i=e[i].next){ int y=e[i].to; if(y==wson[x]||y==fa[x]) continue; dfs2(y,y); } } int LCA(int u,int v){ while(top[u]!=top[v]){ if(depth[top[u]]<depth[top[v]]) swap(u,v); u=fa[top[u]]; } return (depth[u]<depth[v]?u:v); } } using namespace Tree_Chain_Partition; int u[N],v[N],lca[N],len[N]; int b[N]; int sum,reduce; void dfs(int x){ for(int i=head[x];i;i=e[i].next){ int y=e[i].to; if(y==fa[x]) continue; dfs(y); b[x]+=b[y]; } if(b[x]==sum) reduce=max(reduce,e[edge[x]].t); } bool check(int x){ for(int i=1;i<=n;i++) b[i]=0; sum=reduce=0; for(int i=1;i<=m;i++){ if(len[i]>x){ sum++; b[u[i]]++; b[v[i]]++; b[lca[i]]-=2; } } dfs(1); if(MAX-reduce<=x) return 1; return 0; } int ans; signed main() { #ifndef ONLINE_JUDGE freopen("lty.in","r",stdin); freopen("lty.out","w",stdout); #endif n=read,m=read; for(int a,b,c,i=1;i<n;i++){ a=read,b=read,c=read; add(a,b,c),add(b,a,c); } dfs1(1),dfs2(1,1); for(int i=1;i<=m;i++){ u[i]=read,v[i]=read; lca[i]=LCA(u[i],v[i]); len[i]=dis[u[i]]+dis[v[i]]-2*dis[lca[i]]; MAX=max(MAX,len[i]); } int st=0,ed=MAX; while(st<ed){ int mid=(st+ed)>>1; if(check(mid)) ed=ans=mid; else st=mid+1; } write(ans); return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!