什么是树链剖分?
对于一(heng)些(duo)在树上对路径维护的题,就算暴力倍增找lca也会TLE。为此,我们以某种规则将一棵树剖分成若干条竖直方向上的链,每次维护时可以一次跳一条链、并借助一些强大的线性数据结构来维护(通常链的数量很少),这样就大大优化了时间复杂度,足以解决很多线性结构搬到树上的题目。
前置知识:
以线段树为代表的各种强大的数据结构;dfs序(化树形为线性)
学习前先了解一些新概念:
重儿子:父亲节点的所有儿子中子树结点数目最多(size最大)的结点;
轻儿子:父亲节点中除了重儿子以外的儿子;
重边:父亲结点和重儿子连成的边;
轻边:父亲节点和轻儿子连成的边;
重链:由多条重边连接而成的路径;
轻链:由多条轻边连接而成的路径;
通常我们剖分的规则是重链剖分法,即先把重儿子分到一个链上,再去管其余轻儿子。这样做在大多数情况下得到的长链较多,而且有经证明的时间复杂度上限。
这样肯定要先来一遍dfs找到所有重儿子,并预处理一些点的基本信息:深度、父亲节点、字树大小;之后再来一遍dfs,剖分树链(记录每个点的链头),并记录此时的dfs序,将树的节点需维护的信息按dfs序存到一个线性结构中维护。两遍dfs后就在线性结构的上面建一棵线段树(一般都是线段树(毕竟太强大了、太容易被考了),有时换成树状数组也行,看题目而定啦)。
见代码:
1 void dfs1(int u,int fa,int deep)//第一遍dfs 2 { 3 dep[u]=deep;//深度 4 siz[u]=1;//子树大小一开始为1(它自己) ,之后再加它儿子的子树大小就得到的它真正的子树大小了 5 int maxs=0,maxd=0; 6 int t; 7 for(int e=lst[u];e;e=nxt[e]) 8 { 9 t=to[e]; 10 if(t!=fa) 11 { 12 father[t]=u; 13 dfs1(t,u,deep+1); 14 siz[u]+=siz[t]; 15 if(siz[t]>maxs)//找重儿子 16 { 17 maxs=siz[t]; 18 maxd=t; 19 } 20 } 21 } 22 son[u]=maxd;//重儿子 23 } 24 25 int dfsxu; 26 27 void dfs2(int u,int fa,int ding)//第二遍dfs 28 { 29 top[u]=ding;//链头 30 dfs[u]=++dfsxu;//记录dfs序 31 xu[dfsxu]=num[u];//按dfs序记录的点要维护的信息 32 if(!son[u]) 33 return; 34 dfs2(son[u],u,ding);//先把重儿子纳入父亲的链中 35 int t; 36 for(int e=lst[u];e;e=nxt[e]) 37 { 38 t=to[e]; 39 if(t!=fa&&t!=son[u])//其他轻儿子自辟一链 40 dfs2(t,u,t); 41 } 42 }
我们看看dfs序,发现同一条树链上的节点dfs序都是连续的,同时以一个点为根的子树内所有点的dfs序也是连续的,这样导致什么结果?你若想维护一棵子树或一条树链上的信息,只需知道最小和最大的dfs序,便可用数据结构维护这个区间,缩小维护的时间复杂度(比暴力什么的好太多啦)。
之后维护时怎么办?这里单讲维护x到y路径的情况(维护以u为根的子树的话只要维护区间[dfs[u],dfs[u]+siz[u]-1]就好了):
分两种情况:
1、x、y在同一链上(链头相等),只要维护浅(深度小)的点的dfs序到另一个点的dfs序之间的区间就结束了
2、x、y不在同一条链上,那么就让x或y往上跳,跳到同一个链上为止。跳那个呢?发现两点最终肯定不会同在x和y所在链的链头深度深的那个链,但有可能同在x和y所在链的链头深度浅的那个链,设这时dep[top[x]]>=dep[top[y]](如果假设不成立就交换x、y),把x跳到它的链头的父亲上,期间它会越过若干点,这若干点组成的dfs序的区间(包括x但不包括x跳完后所在的点)即为[dfs[top[x],dfs[x]],维护一下这个区间就好。以此规则不断跳,终能转化到第一种情况。
查询与维护跳点的思路一致,将维护区间改为查询区间就好了。
核心代码:
1 int x,y,z; 2 x=read(),y=read(),z=read();//将x到y路径所有点的点权加z 3 while(top[x]!=top[y]) 4 { 5 if(dep[top[x]]<dep[top[y]]) swap(x,y); 6 modify(1,1,n,dfs[top[x]],dfs[x],z); 7 x=father[top[x]]; 8 } 9 if(dep[x]>dep[y]) swap(x,y); 10 modify(1,1,n,dfs[x],dfs[y],z);//线段树区间加
1 int x,y,z=0;//查询x到y路径所有点的点权之和 2 x=read(),y=read(); 3 while(top[x]!=top[y]) 4 { 5 if(dep[top[x]]<dep[top[y]]) swap(x,y); 6 z+=fin(1,1,n,dfs[top[x]],dfs[x]); 7 x=father[top[x]]; 8 } 9 if(dep[x]>dep[y]) swap(x,y); 10 z+=fin(1,1,n,dfs[x],dfs[y]); 11 printf("%d\n",z%mod);
完整代码:
1 #include2 #include 3 #include 4 5 using namespace std; 6 7 const int N=100000,M=100000; 8 9 int x,n,m,root,mod,num[N+5],tree[N<<2+5],dep[N+5],son[N+5],dfs[N+5],xu[N+5],top[N+5],siz[N+5]; 10 11 int father[N+5],lst[N+5],to[N*2],nxt[N*2],cnt,lzy[N<<2]; 12 13 char ch; 14 15 inline int read() 16 { 17 x=0; 18 ch=getchar(); 19 while(!isdigit(ch)) ch=getchar(); 20 while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=getchar(); 21 return x; 22 } 23 24 inline void addedge(int u,int v) 25 { 26 nxt[++cnt]=lst[u]; 27 lst[u]=cnt; 28 to[cnt]=v; 29 } 30 31 void dfs1(int u,int fa,int deep) 32 { 33 dep[u]=deep; 34 siz[u]=1; 35 int maxs=0,maxd=0; 36 int t; 37 for(int e=lst[u];e;e=nxt[e]) 38 { 39 t=to[e]; 40 if(t!=fa) 41 { 42 father[t]=u; 43 dfs1(t,u,deep+1); 44 siz[u]+=siz[t]; 45 if(siz[t]>maxs) 46 { 47 maxs=siz[t]; 48 maxd=t; 49 } 50 } 51 } 52 son[u]=maxd; 53 } 54 55 int dfsxu; 56 57 void dfs2(int u,int fa,int ding) 58 { 59 top[u]=ding; 60 dfs[u]=++dfsxu; 61 xu[dfsxu]=num[u]; 62 if(!son[u]) 63 return; 64 dfs2(son[u],u,ding); 65 int t; 66 for(int e=lst[u];e;e=nxt[e]) 67 { 68 t=to[e]; 69 if(t!=fa&&t!=son[u]) 70 dfs2(t,u,t); 71 } 72 } 73 74 inline void updata(int t) 75 { 76 tree[t]=(tree[t<<1]+tree[t<<1|1])%mod; 77 } 78 79 void build(int t,int l,int r) 80 { 81 if(l==r) 82 { 83 tree[t]=xu[l]%mod;return; 84 } 85 build(t<<1,l,(l+r)>>1); 86 build(t<<1|1,((l+r)>>1)+1,r); 87 updata(t); 88 } 89 90 inline void init() 91 { 92 for(int i=1;i<=n;i++) num[i]=read(); 93 int u,v; 94 for(int i=1;i i) 95 { 96 u=read(),v=read(); 97 addedge(u,v); 98 addedge(v,u); 99 } 100 dfs1(root,-1,1); 101 dfs2(root,-1,root); 102 build(1,1,n); 103 } 104 105 inline void pushdown(int t,int l,int r) 106 { 107 if(!lzy[t]) return; 108 lzy[t<<1]=(lzy[t<<1]+lzy[t])%mod; 109 lzy[t<<1|1]=(lzy[t<<1|1]+lzy[t])%mod; 110 int mid=(l+r)>>1; 111 tree[t<<1]=(tree[t<<1]+(mid-l+1)*lzy[t])%mod; 112 tree[t<<1|1]=(tree[t<<1|1]+(r-mid)*lzy[t])%mod; 113 lzy[t]=0; 114 } 115 116 void modify(int t,int l,int r,int x,int y,int a) 117 { 118 if(x<=l&&r<=y) 119 { 120 lzy[t]=(lzy[t]+a)%mod; 121 tree[t]=(tree[t]+(r-l+1)*a)%mod; 122 return; 123 } 124 pushdown(t,l,r); 125 int mid=(l+r)>>1; 126 if(x<=mid) modify(t<<1,l,mid,x,y,a); 127 if(y>mid) modify(t<<1|1,mid+1,r,x,y,a); 128 updata(t); 129 } 130 131 int fin(int t,int l,int r,int x,int y) 132 { 133 if(x<=l&&r<=y) 134 return tree[t]; 135 pushdown(t,l,r); 136 int mid=(l+r)>>1,ans=0; 137 if(x<=mid) ans+=fin(t<<1,l,mid,x,y); 138 if(y>mid) ans+=fin(t<<1|1,mid+1,r,x,y); 139 return ans%mod; 140 } 141 142 inline void gan1() 143 { 144 int x,y,z; 145 x=read(),y=read(),z=read(); 146 while(top[x]!=top[y]) 147 { 148 if(dep[top[x]]<dep[top[y]]) swap(x,y); 149 modify(1,1,n,dfs[top[x]],dfs[x],z); 150 x=father[top[x]]; 151 } 152 if(dep[x]>dep[y]) swap(x,y); 153 modify(1,1,n,dfs[x],dfs[y],z); 154 } 155 156 inline void gan2() 157 { 158 int x,y,z=0; 159 x=read(),y=read(); 160 while(top[x]!=top[y]) 161 { 162 if(dep[top[x]]<dep[top[y]]) swap(x,y); 163 z+=fin(1,1,n,dfs[top[x]],dfs[x]); 164 x=father[top[x]]; 165 } 166 if(dep[x]>dep[y]) swap(x,y); 167 z+=fin(1,1,n,dfs[x],dfs[y]); 168 printf("%d\n",z%mod); 169 } 170 171 inline void solve() 172 { 173 int type,x,y; 174 for(int i=1;i<=m;++i) 175 { 176 type=read(); 177 switch(type) 178 { 179 case 1:gan1();break; 180 case 2:gan2();break; 181 case 3: 182 x=read(),y=read(); 183 modify(1,1,n,dfs[x],dfs[x]+siz[x]-1,y); 184 break; 185 case 4: 186 x=read(); 187 printf("%d\n",fin(1,1,n,dfs[x],dfs[x]+siz[x]-1)); 188 break; 189 } 190 } 191 } 192 193 int main() 194 { 195 n=read(),m=read(),root=read(),mod=read(); 196 init(); 197 solve(); 198 return 0; 199 }
模板题:洛谷P3384 【模板】树链剖分