树链剖分

树链剖分有什么用

我们经常会写到这样的毒瘤题目:给你一棵树,每次对树上的一条链进行操作。  

例如洛咕:P3384 【模板】树链剖分

实现方法

那么我们怎么很快的处理这样的问题呢?

学过倍增LCA的同学应该清楚,就算是倍增,也只能找出它的几代父亲,而不能对路径上的每一个点进行修改。

我们发现对于树上链的操作是很复杂的,因为树上的节点储存是不连续的。

然后你就能拍脑袋了。

既然不连续的区间修改起来很麻烦,那么有没有方法把它变成连续的呢?

 

我们发现对于一棵树,我们对节点的储存顺序是没有要求的。

对于一个父节点,我们只需要知道他儿子们的储存位置;对于一个子节点,我们只要知道他父亲的储存位置,我们就可以存储这棵树了。

那么我们可以通过给这棵树进行重标号,把树上的链化为连续的区间,我们就能很轻松地处理一段链的修改,查询操作了。

 

下面有几个定义:

1.重儿子:一个节点的儿子节点里面,以重儿子为根的子树大小最大。

2.轻儿子:一个节点的儿子节点,除了唯一一个重儿子,其余儿子为轻儿子。

3.重边:连接父亲和重儿子的边称为重边。

4.轻边:连接父亲和轻儿子的边称为轻边。

5.重链:只由重边构成的链称为重链。

 

我们只要把一棵树上的重链全部按顺序储存,就能把树上的问题转化为区间上的问题。

那么接下来用线段树就能轻松维护这个区间问题了。

时间复杂度证明

我们已经学会给树解剖了(雾)

那么我们怎么知道按照重链解剖,时间复杂度会变得更优呢?

1.每走过一个轻边,子树的节点数至少减少一半

我们假设树上一个节点有$n$个儿子,那么由于重儿子子树大小是最大的,当轻儿子的子树大小最大的时候,轻儿子的子树大小应该为\frac{1}{n}$。

我们可以用抽屉原理证明。

那么轻儿子子树大小之多为父亲的$\frac{1}{2}$

2.一条链上最多有$2logn$条重链

 这个也是很好证明的。(虽然是伪证)

我们考虑什么时候链最多。

我们先考虑半链。

发现重链的数量只和轻边有关。

因为重链的两端所连的节点不可能再连接其他的重边,不然长度就会增加。

那么一段链上的重链数就等于轻边数$+1$。

根据结论$1$,一个节点数为$n$的树,最多走$logn$次轻边,节点数就会变成$1$。

换句话说,一条半链上最多有$logn$条轻边。

那么一条全链上最多有$2logn$条重链。

3.单次修改,查询的操作为$log^2n$

我们每次查询可以修改一条链从链底到链顶的一段区间。

根据结论$2$我们知道,单次修改的线段树操作次数为$logn$次,而线段树的时间复杂度为$logn$。

总的时间复杂度就是$log^2n$

具体的实现细节

我们先对树进行预处理,把树重标号以及剖分出重链。

这个预处理由两个dfs构成。

第一个dfs,我们可以计算出以下几个值。

1.每个节点的子树大小$siz[x]$

2.每个节点的深度$dep[x]$

3.每个节点的父亲$fa[x]$

4.每个节点的重儿子$son[x]$

 1 void dfs1(int x,int pre,int deep){
 2     dep[x]=deep;siz[x]=1;
 3     for(int i=head[x];i;i=nxt[i]){
 4         if(ver[i]==pre)continue;
 5         fa[ver[i]]=x;
 6         dfs1(ver[i],x,deep+1);
 7         siz[x]+=siz[ver[i]];
 8         if(siz[son[x]]<siz[ver[i]])son[x]=ver[i];
 9     }
10 }
View Code

 然后第二个dfs,我们给每个节点重新标号。

统计出以下数组。

1.每个节点在线段树上的位置$id[x]$

2.线段树某个位置上的权值$rw[x]$

3.每个节点所在链的链顶节点$top[x]$

为了保证重链上的点储存位置连续,我们要先对重链进行标号。

 1 void dfs2(int x,int ltp){
 2     id[x]=++cnt;
 3     a[cnt]=w[x];
 4     top[x]=ltp;
 5     if(son[x])dfs2(son[x],ltp);
 6     for(int i=head[x];i;i=nxt[i]){
 7         if(ver[i]==son[x]||ver[i]==fa[x])continue;
 8         dfs2(ver[i],ver[i]);
 9     }
10 }
View Code

可能比较抽象,我们可以通过一张图来看。

红色框住的为重链,黑色标号为原来的编号,棕色标号为线段树位置。

我们很容易发现重链上的节点是连续的,子树内的节点是连续的。

那么我们就很容易进行维护了。

至于线段树部分,就不详细解释了。直接看代码就好了

代码

  1 #include <bits/stdc++.h>
  2 using namespace std;
  3 const int N=1e5+100;
  4 int read(){
  5     char c;int num,f=1;
  6     while(c=getchar(),!isdigit(c))if(c=='-')f=-1;num=c-'0';
  7     while(c=getchar(), isdigit(c))num=num*10+c-'0';
  8     return f*num;
  9 }
 10 int mod=1e9+7;
 11 int n,m,r,p;
 12 int w[N],dep[N],fa[N],son[N],id[N],top[N];
 13 int head[N],nxt[N*2],ver[N*2],tot=1,siz[N],cnt=0;
 14 void up(int &x,int y){
 15     x+=y;
 16     if(x>=mod)x-=mod;
 17 }
 18 //线段树
 19 int laz[N*4],tree[N*4],a[N];
 20 /*void build(int l,int r,int rt){
 21     for(int i=1;i<=n;i++)tree[i]=a[i];
 22 }
 23 void modify(int l,int r,int L,int R,int rt,int x){
 24     for(int i=L;i<=R;i++)up(tree[i],x);
 25 }
 26 int query(int l,int r,int L,int R,int rt){
 27     int ans=0;
 28     for(int i=L;i<=R;i++)up(ans,tree[i]);
 29     return ans;
 30 }*/
 31 
 32 void update(int rt){
 33     tree[rt]=tree[rt<<1]+tree[rt<<1|1];
 34     if(tree[rt]>=mod)tree[rt]-=mod;
 35 }
 36 void build(int l,int r,int rt){
 37     if(l==r){tree[rt]=a[l]%mod;return ;}
 38     int mid=(l+r)>>1;
 39     build(l,mid,rt<<1);
 40     build(mid+1,r,rt<<1|1);
 41     update(rt);
 42 }
 43 void pushdown(int rt,int len){
 44     up(laz[rt<<1],laz[rt]);
 45     up(tree[rt<<1],laz[rt]*(len-(len>>1))%mod);
 46     up(laz[rt<<1|1],laz[rt]);
 47     up(tree[rt<<1|1],laz[rt]*(len>>1)%mod);
 48     laz[rt]=0;
 49 }
 50 int query(int l,int r,int L,int R,int rt){
 51     if(L<=l&&r<=R)return tree[rt];
 52     int mid=(l+r)>>1,ans=0;
 53     pushdown(rt,r-l+1);
 54     if(L<=mid)up(ans,query(l,mid,L,R,rt<<1));
 55     if(mid+1<=R)up(ans,query(mid+1,r,L,R,rt<<1|1));
 56     return ans;
 57 }
 58 void modify(int l,int r,int L,int R,int rt,int x){
 59     if(L<=l&&r<=R){
 60         up(tree[rt],x*(r-l+1)%mod);
 61         up(laz[rt],x);
 62         return ;
 63     }
 64     int mid=(l+r)>>1;
 65     pushdown(rt,r-l+1);
 66     if(L<=mid)modify(l,mid,L,R,rt<<1,x);
 67     if(mid+1<=R)modify(mid+1,r,L,R,rt<<1|1,x);
 68     update(rt);
 69 }
 70 
 71 
 72 void add_edge(int u,int v){
 73     ver[++tot]=v;nxt[tot]=head[u];head[u]=tot;
 74     ver[++tot]=u;nxt[tot]=head[v];head[v]=tot;
 75 }
 76 void dfs1(int x,int pre,int deep){
 77     dep[x]=deep;siz[x]=1;
 78     for(int i=head[x];i;i=nxt[i]){
 79         if(ver[i]==pre)continue;
 80         fa[ver[i]]=x;
 81         dfs1(ver[i],x,deep+1);
 82         siz[x]+=siz[ver[i]];
 83         if(siz[son[x]]<siz[ver[i]])son[x]=ver[i];
 84     }
 85 }
 86 void dfs2(int x,int ltp){
 87     id[x]=++cnt;
 88     a[cnt]=w[x];
 89     top[x]=ltp;
 90     if(son[x])dfs2(son[x],ltp);
 91     for(int i=head[x];i;i=nxt[i]){
 92         if(ver[i]==son[x]||ver[i]==fa[x])continue;
 93         dfs2(ver[i],ver[i]);
 94     }
 95 }
 96 void link_modify(int x,int y,int val){
 97     while(top[x]!=top[y]){
 98         if(dep[top[x]]<dep[top[y]])swap(x,y);
 99         modify(1,n,id[top[x]],id[x],1,val);
100         x=fa[top[x]];
101     }
102     if(dep[x]<dep[y])swap(x,y);
103     modify(1,n,id[y],id[x],1,val);
104 }
105 int link_query(int x,int y){
106     int ans=0;
107     while(top[x]!=top[y]){
108         if(dep[top[x]]<dep[top[y]])swap(x,y);
109         up(ans,query(1,n,id[top[x]],id[x],1));
110         x=fa[top[x]];
111     }
112     if(dep[x]<dep[y])swap(x,y);
113     up(ans,query(1,n,id[y],id[x],1));
114     return ans;
115 }
116 void tree_modify(int x,int val){
117     modify(1,n,id[x],id[x]+siz[x]-1,1,val%mod);
118 }
119 int tree_query(int x){
120     return query(1,n,id[x],id[x]+siz[x]-1,1);
121 }
122 int main()
123 {
124     //freopen("data.in","r",stdin);
125     n=read();m=read();
126     r=read();mod=read();
127     for(int i=1;i<=n;i++)w[i]=read();
128     for(int i=1;i< n;i++)add_edge(read(),read());
129     dfs1(r,r,1);dfs2(r,r);build(1,n,1);
130     for(int i=1;i<=m;i++){
131         int opt=read(),x,y,z;
132         if(opt==1){
133             x=read();y=read();z=read();
134             link_modify(x,y,z);
135         }else if(opt==2){
136             x=read();y=read();
137             printf("%d\n",link_query(x,y));
138         }else if(opt==3){
139             x=read();y=read();
140             tree_modify(x,y);
141         }else if(opt==4){
142             x=read();
143             printf("%d\n",tree_query(x));
144         }
145     }
146     //cout<<siz[1]<<endl;
147     /*for(int i=1;i<=10;i++)a[i]=i;
148     build(1,10,1);
149     modify(1,10,1,10,1,10);
150     cout<<query(1,10,1,3,1)<<endl;
151     modify(1,10,2,9,1,10);
152     cout<<query(1,10,1,3,1)<<endl;
153     modify(1,10,1,2,1,10);
154     cout<<query(1,10,1,3,1)<<endl;*/
155     return 0;
156 }
View Code

 

posted @ 2018-11-17 15:49  _onglu  阅读(284)  评论(0编辑  收藏  举报