树链剖分
树链剖分
定义:
树链剖分,又称“重链剖分”。我们将树中的边分为轻边和重边。定义Size(x) 为以 x 为根的子树的节点个数,令 x 的儿子中 y 的Size()最大,那么,我们称边(x,y)为重边,y为x的重儿子。而由重边构成的链即为重链。
性质:
从根到某一点的路径上,轻边不超过O(logN)条,重链也不超过 O(logN)条。
实现:
进行两次DFS
1.找出各个节点的重儿子;
2.找出重链。
我们从根节点开始,向重儿子走,形成一条条重链;向轻儿子走,则以轻儿子开始,形成新一条条重链。
值得注意的是,我们在第二次DFS时,需要给各个节点重新编号,我们记为$nid[x]$,这样可以让一条重链的点的编号,是一段连续的区间,便于用数据结构维护。
小用处:求$LCA$
我们要求$lca(x,y)$,主需要将x,y不断滴沿着重链向上跳,知道他们俩属于同一条重链时,比较一下x,y深度,深度较小的即为$lca$。
例题:
例题一:树的统计
一树上有 n 个节点,编号分别为 1 到 n,每个节点都有一个权值 w。我们将以下面的形式来要求你对这棵树完成一些操作: CHANGE u t :把节点 u 权值改为 t; QMAX u v :询问点 u 到点 v 路径上的节点的最大权值; QSUM u v :询问点 u 到点 v 路径上的节点的权值和。 注意:从点 u 到点 v 路径上的节点包括 u 和 v 本身。
对于 100% 的数据 ,有 1≤n≤3×10e4,0≤q≤2×10e5 。
中途操作中保证每个节点的权值 w 在 −30000 至 30000 之间。
分析:
线段树分别维护两种询问:最大值以及权值和。然后询问一条路径时,只需要把该路径,分成若干条重链,即可用线段树维护以及查询了。
#include<bits/stdc++.h> using namespace std; #define re register int const int N=400005; int tt, las[N], ed[N<<1], nt[N<<1]; void add(int x,int y){ ed[++tt]=y;nt[tt]=las[x];las[x]=tt; } int dep[N], Nid[N], fa[N], son[N], sz[N], Top[N], val[N], Label; struct setment{int mx=-1e9,v=0;}tr[N<<3]; int Qsum(int p,int l,int r,int x,int y) { if(x<=l && r<=y)return tr[p].v; int ret=0, mid=(l+r)>>1; if(x<=mid)ret += Qsum(p<<1, l, mid, x, y); if(y>mid)ret += Qsum(p<<1|1, mid+1, r, x, y); return ret; } int Qmax(int p,int l,int r,int x,int y) { if(x<=l && r<=y)return tr[p].mx; int ret=-1e9, mid=(l+r)>>1; if(x<=mid)ret=max(ret, Qmax(p<<1, l, mid, x, y)); if(y>mid)ret=max(ret, Qmax(p<<1|1, mid+1, r, x, y)); return ret; } void modi(int p,int l,int r,int x,int d) { if(l==x && x==r) { tr[p].mx=d; tr[p].v=d; return; } int mid=(l+r)>>1; if(x<=mid)modi(p<<1, l, mid, x, d); else modi(p<<1|1, mid+1, r, x, d); tr[p].v = tr[p<<1].v+tr[p<<1|1].v; tr[p].mx = max(tr[p<<1].mx, tr[p<<1|1].mx); } int n; void hvedge(int x) { dep[x]=dep[fa[x]]+1; sz[x]=1; for(re i=las[x];i;i=nt[i]) { int v=ed[i]; if(v!=fa[x]) { fa[v]=x; hvedge(v); sz[x]+=sz[v]; if(sz[v]>sz[son[x]])son[x]=v; } } } void hvline(int x,int ance) { Top[x]=ance; Nid[x]=++Label; if(son[x]) hvline(son[x], ance); for(re i=las[x];i;i=nt[i]) if(ed[i]!=son[x]&&ed[i]!=fa[x])hvline(ed[i],ed[i]); } int Qsum(int x,int y) { int ret=0; while(Top[x]!=Top[y]) { if(dep[Top[x]]<dep[Top[y]])swap(x,y); ret += Qsum(1, 1, n, Nid[Top[x]], Nid[x]); x=fa[Top[x]]; } if(dep[x]>dep[y])swap(x, y); ret += Qsum(1, 1, n, Nid[x], Nid[y]); return ret; } int Qmax(int x,int y) { int ret=-1e9; while(Top[x]!=Top[y]) { if(dep[Top[x]]<dep[Top[y]])swap(x,y); ret = max(ret, Qmax(1, 1, n, Nid[Top[x]], Nid[x])); x=fa[Top[x]]; } if(dep[x]>dep[y])swap(x, y); ret = max(ret, Qmax(1, 1, n, Nid[x], Nid[y])); return ret; } char ch[10]; int main() { scanf("%d",&n); for(re i=1;i<n;++i) { int x, y; scanf("%d%d",&x,&y); add(x, y); add(y, x); } hvedge(1); hvline(1, 1); for(re i=1,x;i<=n;++i) { scanf("%d",&x); modi(1,1,n,Nid[i],x); } int T;scanf("%d",&T); while(T--) { int x, y; scanf("%s%d%d",ch,&x,&y); if(ch[0]=='Q'&&ch[1]=='M')printf("%d\n",Qmax(x, y)); if(ch[0]=='Q'&&ch[1]=='S')printf("%d\n",Qsum(x, y)); if(ch[0]=='C')modi(1,1,n,Nid[x],y); } return 0; }
例题二:种草
简要题意:已知一颗树有n个节点,我们会将一条路径经过的点的点权加一,或者求一条路径的点权综合。
分析:树链剖分+线段树维护区间和
#include<bits/stdc++.h> using namespace std; #define re register int const int N=400005; int tt, las[N], ed[N<<1], nt[N<<1]; inline void add(const int x,const int y) { ed[++tt]=y;nt[tt]=las[x];las[x]=tt; } int dep[N], Nid[N], fa[N], son[N], sz[N], Top[N], Label; int va[N<<3], lazy[N<<3]; inline void putdown(const int p) { int x=lazy[p]; lazy[p]=0; int ls=(p<<1), rs=(p<<1|1); va[ls]+=x; va[rs]+=x; lazy[ls]+=x; lazy[rs]+=x; } int Qsum(const int p,const int l,const int r,const int x,const int y) { if(x<=l && r<=y)return va[p]; if(lazy[p]) putdown(p); int ret=0, mid=(l+r)>>1; if(x<=mid)ret += Qsum(p<<1, l, mid, x, y); if(y>mid)ret += Qsum(p<<1|1, mid+1, r, x, y); return ret; } void modi(const int p,const int l,const int r,const int x,const int y) { if(x<=l && r<=y) { va[p]++; lazy[p]++; return; } if(lazy[p])putdown(p); int mid=(l+r)>>1; if(x<=mid) modi(p<<1, l, mid, x, y); if(y>mid) modi(p<<1|1, mid+1, r, x, y); va[p] = va[p<<1] + va[p<<1|1]; } int n; void hvedge(const int x) { dep[x]=dep[fa[x]]+1; sz[x]=1; for(re i=las[x];i;i=nt[i]) { int v=ed[i]; if(v!=fa[x]) { fa[v]=x; hvedge(v); sz[x]+=sz[v]; if(sz[v]>sz[son[x]])son[x]=v; } } } void hvline(const int x,const int ance) { Top[x]=ance; Nid[x]=++Label; if(son[x]) hvline(son[x], ance); for(re i=las[x];i;i=nt[i]) if(ed[i]!=son[x]&&ed[i]!=fa[x])hvline(ed[i],ed[i]); } inline int Qsum(int x,int y) { int ret=0; while(Top[x]!=Top[y]) { if(dep[Top[x]]<dep[Top[y]])swap(x, y); ret += Qsum(1, 1, n, Nid[Top[x]], Nid[x]); x=fa[Top[x]]; } if(dep[x]>dep[y])swap(x, y); ret += Qsum(1, 1, n, Nid[x]+1, Nid[y]); return ret; } inline void modi(int x,int y) { while(Top[x]!=Top[y]) { if(dep[Top[x]]<dep[Top[y]])swap(x, y); modi(1, 1, n, Nid[Top[x]], Nid[x]); x=fa[Top[x]]; } if(dep[x]>dep[y])swap(x, y); modi(1, 1, n, Nid[x]+1, Nid[y]); } char ch[10]; int main() { int m;scanf("%d%d",&n,&m); for(re i=1;i<n;++i) { int x, y; scanf("%d%d",&x,&y); add(x, y); add(y, x); } hvedge(1); hvline(1, 1); while(m--) { int x, y; scanf("%s%d%d",ch,&x,&y); if(ch[0]=='P') modi(x, y); else printf("%d\n", Qsum(x, y)); } return 0; }
例题三:染色
给定一棵有个节点的无根树和m个操作,操作有2类: 1、将节点a到节点b路径上所有点都染成颜色c; 2、询问节点a到节点b路径上的颜色段数量(连续相同颜色被认为是同一段)
如“112221”由3段组成:“11”、“222”和“1”。 请你写一个程序依次完成这m个操作。
分析:线段树维护区间颜色段数,同时记录区间左右端颜色,以助于求区间颜色段数。
#include<bits/stdc++.h> using namespace std; #define re register int const int N=400005; int tt,las[N],ed[N<<1],nt[N<<1],dep[N],Nid[N],fa[N],son[N],sz[N],Top[N],Label,col[N],a[N]; inline void add(const int x,const int y){ed[++tt]=y;nt[tt]=las[x];las[x]=tt;} struct segment{int v,ls,rs,a,b,lazy;}tr[N<<3]; void update(int p) { tr[p].ls=tr[p<<1].ls;tr[p].rs=tr[p<<1|1].rs; tr[p].v=tr[p<<1].v+tr[p<<1|1].v-(tr[p<<1].rs==tr[p<<1|1].ls); } void putdown(int p) { int d=tr[p].lazy;tr[p].lazy=0; tr[p<<1].v=tr[p<<1|1].v=1; tr[p<<1].ls=tr[p<<1].rs=tr[p<<1|1].ls=tr[p<<1|1].rs=d; tr[p<<1].lazy=tr[p<<1|1].lazy=d; } void build(int p,int x,int y) { tr[p].a=x;tr[p].b=y; if(x!=y) { int mid=(x+y)>>1; build(p<<1, x, mid); build(p<<1|1, mid+1, y); update(p); } else{tr[p].rs=tr[p].ls=col[x];tr[p].v=1;return;} } int query(int p,int x,int y) { if(x<=tr[p].a&&tr[p].b<=y)return tr[p].v; if(tr[p].lazy)putdown(p); int mid=(tr[p].a+tr[p].b)>>1; if(x<=mid && y<=mid)return query(p<<1,x,y); if(mid<x && mid<y)return query(p<<1|1,x,y); return query(p<<1,x,y)+query(p<<1|1,x,y)-(tr[p<<1].rs==tr[p<<1|1].ls); } int gt(int p,int x,int d) { if(tr[p].a==x&&x==tr[p].b)return(d==0?tr[p].ls:tr[p].rs); if(tr[p].lazy)putdown(p); int mid=(tr[p].a+tr[p].b)>>1; if(x<=mid)return gt(p<<1, x, d); return gt(p<<1|1, x, d); } void modi(int p,int x,int y,int d) { if(x<=tr[p].a&&tr[p].b<=y){tr[p].v=1;tr[p].ls=tr[p].rs=tr[p].lazy=d;return;} if(tr[p].lazy)putdown(p); int mid=(tr[p].a+tr[p].b)>>1; if(x<=mid)modi(p<<1,x,y,d); if(y>mid)modi(p<<1|1,x,y,d); update(p); } void hvedge(int x) { dep[x]=dep[fa[x]]+1;sz[x]=1; for(re i=las[x],v;i;i=nt[i]) { v=ed[i]; if(v!=fa[x]) { fa[v]=x; hvedge(v); sz[x]+=sz[v]; if(sz[v]>sz[son[x]])son[x]=v; } } } void hvline(int x,int ance) { Top[x]=ance;Nid[x]=++Label; if(son[x]) hvline(son[x], ance); for(re i=las[x];i;i=nt[i])if(ed[i]!=son[x]&&ed[i]!=fa[x])hvline(ed[i],ed[i]); } int lca(int x,int y) { while(Top[x]!=Top[y]) { if(dep[Top[x]]<dep[Top[y]])swap(x, y); x=fa[Top[x]]; } if(dep[x]>dep[y])swap(x, y); return x; } int getans(int x,int y) { int ca=lca(x, y),ret=0; while(Top[x]!=Top[ca]) { ret+=query(1,Nid[Top[x]],Nid[x])-(gt(1,Nid[Top[x]],0) == gt(1,Nid[fa[Top[x]]],1)); x=fa[Top[x]]; } while(Top[y]!=Top[ca]) { ret+=query(1,Nid[Top[y]],Nid[y])-(gt(1,Nid[Top[y]],0) == gt(1,Nid[fa[Top[y]]],1)); y=fa[Top[y]]; } if(dep[x]>dep[y])swap(x,y); ret+=query(1,Nid[x],Nid[y]); return ret; } void Change(int x,int y,int d) { while(Top[x]!=Top[y]) { if(dep[Top[x]]<dep[Top[y]])swap(x, y); modi(1,Nid[Top[x]],Nid[x],d); x=fa[Top[x]]; } if(dep[x]>dep[y])swap(x,y); modi(1,Nid[x],Nid[y],d); } char ch[4]; int main() { int m,n;scanf("%d%d",&n,&m); for(re i=1;i<=n;++i)scanf("%d",&a[i]); for(re i=1, x, y;i<n;++i) { scanf("%d%d",&x,&y); add(x, y); add(y, x); }hvedge(1);hvline(1, 1); for(re i=1;i<=n;++i)col[Nid[i]]=a[i]; build(1, 1, n);int x,y,d; while(m--) { scanf("%s",ch); if(ch[0]=='Q') { scanf("%d%d",&x,&y); printf("%d\n", getans(x, y)); } else { scanf("%d%d%d",&x,&y,&d); Change(x, y, d); } } return 0; }
例题四:旅行
S国有N个城市,编号从1到N。
城市间用N-1条双向道路连接,满足从一个城市出发可以到达其它所有城市。
每个城市信仰不同的宗教,如飞天面条神教、隐形独角兽教、绝地教都是常见的信仰。
为了方便,我们用不同的正整数代表各种宗教。 S国的居民常常旅行。
旅行时他们总会走最短路,并且为了避免麻烦,只在信仰和他们相同的城市留宿。
当然旅程的终点也是信仰与他相同的城市。
S国政府为每个城市标定了不同的旅行评级,旅行者们常会记下途中(包括起点和终点)留宿过的城市的评级总和或最大值。 在S国的历史上常会发生以下几种事件: • "CC x c":城市x的居民全体改信了c教; • "CW x w":城市x的评级调整为w; • "QS x y":一位旅行者从城市x出发,到城市y,并记下了途中留宿过的城市的评级总和; • "QM x y":一位旅行者从城市x出发,到城市y,并记下了途中留宿过的城市的评级最大值。 由于年代久远,旅行者记下的数字已经遗失了,但记录开始之前每座城市的信仰与评级,还有事件记录本身是完好的。
请根据这些信息,还原旅行者记下的数字。 为了方便,我们认为事件之间的间隔足够长,以致在任意一次旅行中,所有城市的评级和信仰保持不变。
分析:各个宗教分别进行线段树维护,但需要动态开点以节约空间
#include<bits/stdc++.h> using namespace std; #define re register int const int N=8e5+5; int tt, las[N], ed[N<<1], nt[N<<1]; void add(int x,int y){ed[++tt]=y;nt[tt]=las[x];las[x]=tt;} int dep[N], fa[N], Top[N], son[N], sz[N], Nid[N], Label; void hvedge(int x) { dep[x]=dep[fa[x]]+1; sz[x]=1; for(re i=las[x];i;i=nt[i]) { int v=ed[i]; if(v!=fa[x]) { fa[v]=x;hvedge(v);sz[x]+=sz[v]; if(sz[v]>sz[son[x]])son[x]=v; } } } void hvline(int x,int ance) { Top[x]=ance;Nid[x]=++Label; if(son[x])hvline(son[x], ance); for(re i=las[x];i;i=nt[i])if(ed[i]!=fa[x]&&ed[i]!=son[x])hvline(ed[i], ed[i]); } int seg; struct segment{int ls, rs, v, mx;}tr[N*32]; void update(int p) { tr[p].mx=tr[p].v=0; if(tr[p].ls)tr[p].v=tr[p].v+tr[tr[p].ls].v, tr[p].mx=max(tr[p].mx, tr[tr[p].ls].mx); if(tr[p].rs)tr[p].v=tr[p].v+tr[tr[p].rs].v, tr[p].mx=max(tr[p].mx, tr[tr[p].rs].mx); } void modi(int &p,int l,int r,int x,int d) { if(!p)p=++seg; if(l==r){tr[p].mx=tr[p].v=d;return;} int mid=(l+r)>>1; if(x<=mid)modi(tr[p].ls, l, mid, x, d); else modi(tr[p].rs, mid+1, r, x, d); update(p); } int Qsum(int p,int l,int r,int x,int y) { if(!p)return 0; if(x<=l&&r<=y)return tr[p].v; int mid=(l+r)>>1, ret=0; if(x<=mid)ret+=Qsum(tr[p].ls, l, mid, x, y); if(y>mid)ret+=Qsum(tr[p].rs, mid+1, r, x, y); return ret; } int Qmax(int p,int l,int r,int x,int y) { if(!p)return 0; if(x<=l&&r<=y)return tr[p].mx; int mid=(l+r)>>1, ret=0; if(x<=mid)ret=max(ret, Qmax(tr[p].ls, l, mid, x, y)); if(y>mid)ret=max(ret, Qmax(tr[p].rs, mid+1, r, x, y)); return ret; } int n, w[N], c[N], root[N]; int Qsum(int x, int y) { int bl=c[x], ret=0; while(Top[x]!=Top[y]) { if(dep[Top[x]]<dep[Top[y]])swap(x, y); ret += Qsum(root[bl], 1, n, Nid[Top[x]], Nid[x]); x=fa[Top[x]]; } if(dep[x]>dep[y])swap(x, y); ret += Qsum(root[bl], 1, n, Nid[x], Nid[y]); return ret; } int Qmax(int x,int y) { int bl=c[x], ret=0; while(Top[x]!=Top[y]) { if(dep[Top[x]]<dep[Top[y]])swap(x, y); ret = max(ret, Qmax(root[bl], 1, n, Nid[Top[x]], Nid[x])); x=fa[Top[x]]; } if(dep[x]>dep[y])swap(x, y); ret = max(ret, Qmax(root[bl], 1, n, Nid[x], Nid[y])); return ret; } char ch[5]; int main() { int m; scanf("%d%d",&n,&m); for(re i=1;i<=n;++i)scanf("%d%d",&w[i],&c[i]); for(re i=1, x, y;i<n;++i) { scanf("%d%d",&x,&y); add(x, y);add(y, x); } hvedge(1);hvline(1,1); for(re i=1;i<=n;++i)modi(root[c[i]], 1, n, Nid[i], w[i]); while(m--) { int x, y;scanf("%s%d%d",ch,&x,&y); if(ch[0]=='C'&&ch[1]=='C'&&c[x]!=y) { modi(root[c[x]], 1, n, Nid[x], 0); c[x]=y; modi(root[c[x]], 1, n, Nid[x], w[x]); } if(ch[0]=='C'&&ch[1]=='W'&&w[x]!=y) { modi(root[c[x]], 1, n, Nid[x], y); w[x]=y; } if(ch[0]=='Q'&&ch[1]=='S')printf("%d\n",Qsum(x, y)); if(ch[0]=='Q'&&ch[1]=='M')printf("%d\n",Qmax(x, y)); } return 0; }

浙公网安备 33010602011771号