dfs序与树链剖分
还是先安利一发AgOH https://space.bilibili.com/120174936
模板题:洛谷 P3384 【模板】轻重链剖分
https://www.luogu.com.cn/problem/P3384
如题,已知一棵包含 N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:
操作 1: 格式: 1 x y z 表示将树从 x 到 y 结点最短路径上所有节点的值都加上 z。
操作 2: 格式: 2 x y 表示求树从 x 到 y 结点最短路径上所有节点的值之和。
操作 3: 格式: 3 x z 表示将以 x 为根节点的子树内所有节点值都加上 z。
操作 4: 格式:4 x 表示求以 x 为根节点的子树内所有节点值之和。
先说dfs序
dfs序,就是dfs的顺序
如图,标记的数字的顺序就是dfs序
再就是,要区分一下dfs序和欧拉序
dfs序是以深度优先搜索的原则遍历图中节点出现的顺序 --->一个节点只出现一次 1,2,3,4,5,6,7,8,9,10
欧拉序是每经过一次节点,就要把他写出来,如图那就应该是1,2,3,4,3,5,3,6,3,2,1,7,8,9,8,7,10
时间戳
时间戳即dfs第一次访问到每个节点的时间,从1开始递加
与上面的图对应,我们称A的时间戳为1,D的时间戳为3,E的时间戳为8
Q:那我们搞这个时间戳是用来干嘛的??
A:我们把树搞成了连续的,方便后续对树进行一些操作(暗指线段树 树状数组啥的QAQ)
我们会发现两个重要的性质:
1一个结点的子树上的结点的时间戳,一定大于这个结点的时间戳且连续
2某些链上的时间戳也是连续的
有了上面的性质,操作3和操作4就可以解决了
把树看成是数组,时间戳是下标 tree[1]='A',tree[2]='B……
操作3:将以x为根结点的子树内所有结点值都加上z ---->区间修改 [x,x+子树的大小]
操作4:求以x为根节点的子树内所有结点值的和--->区间查询 [x,x+子树的大小]
树链剖分
那操作1和操作2呢?
树链剖分:把树拆成若干条不相交的链,可以优化一些 树上路径修改及路径信息查询等问题
本质上,树链剖分是一种将树肢解成链平摊开来,再使用线段树对其进行维护的神奇算法
重链剖分O(logn)
长链剖分O(sqrt(n))
实链剖分(搞LCT)
在这我们先考虑更常用的重链剖分
一些术语:
重儿子:一个结点的所有儿子里最重的一个(只有一个也必须有一个,如果一样重随便找一个)
轻儿子:除了重儿子的所有儿子
重链:从一个轻儿子开始(根节点也算)一路往重儿子走连出的一条链
轻链:除了重链的所有链
下面是我们剖好的树
像这样一棵树,紫色结点为重儿子,紫线连成的链就是重链
紫线两端的结点都属于重链
我们之前提到,这样的树上的某些链上的时间戳是连续的,那我们规定优先往重儿子走了之后,这个某些链就成了重链
至于有什么用,继续往下看:
开始剖分
第一遍dfs:标记
1 结点的父亲 fa[maxn]
2 结点的重儿子 son[maxn]
3 结点的深度 deep[maxn]
4 结点的大小 siz[maxn]
第二遍dfs:标记
1 dfs序和时间戳 (结点权值的dfs序 w[maxn])( 时间戳 dfn[maxn])
2 当前结点所在重链的头头是谁(就那个最上面的轻儿子),头的头是他自己 top[maxn]
还需要计数器tim,维护w的线段树,v[maxn]存放所有节点的权值
void dfs1(int u,int f) { fa[u]=f; deep[u]=deep[f]+1;//deep up up siz[u]=1; int maxsize=-1;//find the weight son for(int i=head[u];~i;i=edge[i].next) { int v=edge[i].to; if(v==f) continue;//don't need the father dfs1(v,u);// v is son, u is father siz[u]+=siz[v]; if(siz[v]>maxsize) { maxsize=siz[v]; son[u]=v; } } } void dfs2(int u,int t) { dfn[u]=++tim; top[u]=t; w[tim]=v[u];//Store the value of the current node in its timestamp array if(!son[u]) return ;// can't find the son/the heaviest son dfs2(son[u],t);// first -> the heaviest son for(int i=head[u];~i;i=edge[i].next) { int v=edge[i].to; if(v==fa[u]||v==son[u]) continue;//father or the heaviest son dfs2(v,v);// not the the heaviest son , the light son is himself } }
操作三和操作四
inline void mson(int x,int z) {//将以x为根节点的子树内所有节点值都加上z modify(dfn[x],dfn[x]+siz[x]-1,z); } inline int qson(int x) {//求以x为根节点的子树内所有节点值的和 return query(dfn[x],dfn[x]+siz[x]-1); }
树链剖分找LCA
先来证明一个定理:除根结点外的任何一个结点的父亲结点都一定在一条重链上
证明:因为父亲结点存在儿子,所以一定存在重儿子,所以-一定在一条重链上
操作一和操作二
操作 1: 格式: 1 x y z 表示将树从 x 到 y 结点最短路径上所有节点的值都加上 z。
操作 2: 格式: 2 x y 表示求树从 x 到 y 结点最短路径上所有节点的值之和。
不难发现,任何一条路径都是由重链的一部分和叶子节点组成
如果要查询的两个结点在同一条重链上,那直接在线段树上查询就好了
如果不在一条重链上,那我们可以考虑把所在重链的top深度大的那个点跳到top处,顺便利用线段树区修,再把top跳到top的父亲,因为之前的那个定理,所以一直循环这个操作,是保证有解的,连续套娃,直到两个数跳到了同一结点或同一深度,over
代码:
1 #include <bits/stdc++.h> 2 using namespace std; 3 #define ll long long 4 #define lowbit(a) ((a)&-(a)) 5 #define clean(a,b) memset(a,b,sizeof(a)) 6 const int inf=0x3f3f3f3f; 7 const int maxn = 1e5+10; 8 const int maxm=maxn*2; 9 int _,mod; 10 11 ///////////////////////////////////////////////////////////////////////////////////////// 12 struct node 13 { 14 int l,r,sum,add; 15 }tree[maxn*4]; 16 int tim,v[maxn],w[maxn];// w -> the node's dfs xu 17 int fa[maxn],deep[maxn],siz[maxn],son[maxn]; 18 int dfn[maxn],top[maxn]; 19 /*********************************链式前向星************************************/ 20 struct E 21 { 22 int to,next; 23 }edge[maxm]; 24 int tot,head[maxn]; 25 inline void addedge(int u,int v) 26 { 27 edge[tot]=(E){v,head[u]}; 28 head[u]=tot++; 29 edge[tot]=(E){u,head[v]}; 30 head[v]=tot++; 31 } 32 /********************************树链剖分大法************************************/ 33 void dfs1(int u,int f) 34 { 35 fa[u]=f; 36 deep[u]=deep[f]+1;//deep up up 37 siz[u]=1; 38 int maxsize=-1;//find the weight son 39 for(int i=head[u];~i;i=edge[i].next) 40 { 41 int v=edge[i].to; 42 if(v==f) continue;//don't need the father 43 dfs1(v,u);// v is son, u is father 44 siz[u]+=siz[v]; 45 if(siz[v]>maxsize) 46 { 47 maxsize=siz[v]; 48 son[u]=v; 49 } 50 } 51 } 52 void dfs2(int u,int t) 53 { 54 dfn[u]=++tim; 55 top[u]=t; 56 w[tim]=v[u];//Store the value of the current node in its timestamp array 57 if(!son[u]) return ;// can't find the son/the heaviest son 58 dfs2(son[u],t);// first -> the heaviest son 59 for(int i=head[u];~i;i=edge[i].next) 60 { 61 int v=edge[i].to; 62 if(v==fa[u]||v==son[u]) continue;//father or the heaviest son 63 dfs2(v,v);// not the the heaviest son , the light son is himself 64 } 65 } 66 /*******************************线段树*******************************************/ 67 void push_up(int now) 68 { 69 tree[now].sum=(tree[now<<1].sum+tree[now<<1|1].sum)%mod; 70 } 71 void push_down(int now) 72 { 73 tree[now<<1].sum+=(tree[now<<1].r-tree[now<<1].l+1)*tree[now].add; 74 tree[now<<1|1].sum+=(tree[now<<1|1].r-tree[now<<1|1].l+1)*tree[now].add; 75 tree[now<<1].sum%=mod; 76 tree[now<<1|1].sum%=mod; 77 tree[now<<1].add+=tree[now].add; 78 tree[now<<1|1].add+=tree[now].add; 79 tree[now].add=0; 80 } 81 void build(int l,int r,int now=1) 82 { 83 tree[now].l=l; 84 tree[now].r=r; 85 tree[now].add=0; 86 if(l==r) 87 { 88 tree[now].sum=w[l]%mod; 89 return ; 90 } 91 int mid=(l+r)>>1; 92 build(l,mid,now<<1); 93 build(mid+1,r,now<<1|1); 94 push_up(now); 95 } 96 void modify(int l,int r,int k,int now=1) 97 { 98 if(l==tree[now].l&&r==tree[now].r) 99 { 100 tree[now].sum+=(r+1-l)*k; 101 tree[now].sum%=mod; 102 tree[now].add+=k; 103 return ; 104 } 105 if(tree[now].add) push_down(now); 106 int mid=(tree[now].l+tree[now].r)>>1; 107 if(r<=mid) modify(l,r,k,now<<1); 108 else if(l>mid) modify(l,r,k,now<<1|1); 109 else 110 { 111 modify(l,mid,k,now<<1); 112 modify(mid+1,r,k,now<<1|1); 113 } 114 push_up(now); 115 } 116 int query(int l,int r,int now=1) 117 { 118 if(l==tree[now].l&&r==tree[now].r) 119 { 120 return tree[now].sum; 121 } 122 if(tree[now].add) push_down(now); 123 int mid=(tree[now].l+tree[now].r)>>1; 124 int sum=0; 125 if(r<=mid) sum+=query(l,r,now<<1); 126 else if(l>mid) sum+=query(l,r,now<<1|1); 127 else 128 { 129 sum+=query(l,mid,now<<1)+query(mid+1,r,now<<1|1); 130 } 131 return sum%mod; 132 } 133 /*******************************操作****************************************/ 134 135 void mchain(int x,int y,int z) 136 {//将树从 x 到 y 结点最短路径上所有节点的值都加上 z 137 z%=mod; 138 while(top[x]!=top[y])//不在一条重链上 139 { 140 if(deep[top[x]]<deep[top[y]]) 141 { 142 swap(x,y); 143 }//保证x的深度最大,每次都把x跳上去 144 modify(dfn[top[x]],dfn[x],z); 145 x=fa[top[x]]; 146 }//此时他俩一定是在一条重链上的 147 if(deep[x]>deep[y]) swap(x,y); 148 modify(dfn[x],dfn[y],z); 149 } 150 int qchain(int x,int y) 151 { 152 int ret=0; 153 while(top[x]!=top[y]) 154 { 155 if(deep[top[x]]<deep[top[y]]) 156 { 157 swap(x,y); 158 }//保证x的深度最大,每次都把x跳上去 159 ret+=query(dfn[top[x]],dfn[x]); 160 x=fa[top[x]]; 161 } 162 if(deep[x]>deep[y]) swap(x,y); 163 ret+=query(dfn[x],dfn[y]); 164 return ret%mod; 165 } 166 inline void mson(int x,int z) 167 {//将以x为根节点的子树内所有节点值都加上z 168 modify(dfn[x],dfn[x]+siz[x]-1,z); 169 } 170 inline int qson(int x) 171 {//求以x为根节点的子树内所有节点值的和 172 return query(dfn[x],dfn[x]+siz[x]-1); 173 } 174 ///////////////////////////////////////////////////////////////////////////////////////// 175 176 int main() 177 { 178 clean(head,-1); 179 int n,m,r; 180 scanf("%d%d%d%d",&n,&m,&r,&mod); 181 for(int i=1;i<=n;i++) scanf("%d",&v[i]); 182 for(int i=1;i<n;i++) 183 { 184 int u,v; 185 scanf("%d%d",&u,&v); 186 addedge(u,v); 187 } 188 dfs1(r,r); 189 dfs2(r,r); 190 build(1,n); 191 while(m--) 192 { 193 int num,x,y,z; 194 scanf("%d",&num); 195 switch(num) 196 { 197 case 1: 198 scanf("%d%d%d",&x,&y,&z); 199 mchain(x,y,z); 200 break; 201 202 case 2: 203 scanf("%d%d",&x,&y); 204 printf("%d ",qchain(x,y)); 205 break; 206 207 case 3: 208 scanf("%d%d",&x,&z); 209 mson(x,z); 210 break; 211 212 case 4: 213 scanf("%d",&x); 214 printf("%d ",qson(x)); 215 break; 216 } 217 } 218 return 0; 219 }