1. 程式人生 > 實用技巧 >【毒鏈剖分】

【毒鏈剖分】

樹鏈剖分

這是個讓初學者望而卻步的東西,不管打了多少次,也很難一遍過(我太弱了)

根據這個樹鋸結構可知,這是個鋸樹結構。它把一棵樹拆分成若干條鏈,從而利用一些其他的資料結構來維護每一條鏈

常見的路徑剖分的方法是輕重樹鏈剖分(啟發式剖分)

那我們先來康康毒鏈剖分有哪些操作吧!

定義

在輕重鏈剖分中,對於一個節點,他的一顆以兒子為根節點,節點數最多的子樹稱為重兒子,其連邊稱為重邊,其餘稱為輕邊,重邊組成的鏈叫做重鏈

所以第一次我們dfs,標記重兒子重邊並且記錄字樹大小和深度

(紅線為與重兒子連邊的重邊,其餘為輕邊)

另外,輕重鏈剖分,從根到某一點的路徑上不超過跳logn次。不信的自己可以手搓搓幾組

預處理

 1 void dfs1(int x,int f){
 2     size[x]=1;//當前節點 
 3     fa[x]=f;//記錄父親 
 4     for(int i=last[x];i;i=e[i].pre)
 5     {
 6         int y=e[i].to;
 7         if(y==f)continue;
 8         deep[y]=deep[x]+1;//記錄深度 
 9         dfs1(y,x);
10         size[x]+=size[y];//更新以x為根節點的子樹大小 
11         if(size[y]>size[son[x]])son[x]=y;//
更新重兒子 12 } 13 }

因為我們要在鏈上維護線段樹(這裡以線段樹為例,當然其他也行)

怎麼做呢,不可能每一條鏈都維護一個吧,絕對會MLE

那我們可以讓每一條重鏈的編號都是有序的,這樣線上段樹上做操作就很方便

怎麼才能有序呢?每次按照重兒子搜尋就好啦

 1 void dfs2(int x,int topf){
 2     id[x]=++cnt;//記錄編號 
 3     top[x]=topf;//記錄鏈的頂端,方便後面跳 
 4     v[cnt]=a[x];//儲存節點資料 
 5     if(!son[x])return;//沒有兒子就返回 
 6     dfs2(son[x],topf);//
先搜尋重兒子,保證重鏈上面的編號是連續的 7 for(int i=last[x];i;i=e[i].pre) 8 { 9 int y=e[i].to; 10 if(y==fa[x]||y==son[x])continue;//搜尋其他兒子 11 dfs2(y,y);//因為不是重兒子了,重鏈斷了,所以重新開一條鏈 12 } 13 }

預處理完了,然後來康康操作啊

操作1: 格式:x y z表示將樹從x到y結點最短路徑上所有節點的值都加上z。

樹上最短路徑想到什麼,LCA啊。但是我們這裡不用倍增啥的,因為有輕重鏈,所以找這個只是log級別的,所以每次一條一條的跳就行了,至於途中的操作,就由線段樹實現啦

void upG(int x,int y,int k){
    while(top[x]!=top[y])//不在同一條鏈 
    {
        if(deep[top[x]]<deep[top[y]])swap(x,y);//保證x深度更大 
        ADD(id[top[x]],id[x],1,n,1,k);//線段樹維護 
        x=fa[top[x]];//跳鏈上去,
    }
    if(deep[x]<deep[y])swap(x,y);
    ADD(id[y],id[x],1,n,1,k);//剩下的直接跳 維護 
}

操作2: 格式:x y表示求樹從x到y結點最短路徑上所有節點的值之和。

這個,和上面的操作差不多啊,只是線段樹的操作改了一下

 1 int getG(int x,int y){
 2     int res=0;
 3     while(top[x]!=top[y])//和上面一樣的操作 
 4     {
 5         if(deep[top[x]]<deep[top[y]])swap(x,y);
 6         res+=getsum(id[top[x]],id[x],1,n,1);//線段樹解決這條鏈 
 7         x=fa[top[x]];
 8     }
 9     if(deep[x]<deep[y])swap(x,y);
10     res+=getsum(id[y],id[x],1,n,1);
11     return res;
12 }

操作3: 格式:x z表示將以x為根節點的子樹內所有節點值都加上z。

首先,我們如何確定這顆子樹的編號是否是連續的呢,以及如何求到這顆子樹的範圍呢?

因為我們是深搜,所以一個根節點的子樹一定都在他的後面被搜尋到,並且中途一定不會跳到其他地方,所以最後一個的編號是根節點編號+子樹節點數-1(減掉自己)

1 void upSON(int x,int k){
2     ADD(id[x],id[x]+size[x]-1,1,n,1,k);//根節點編號和最後一個編號 
3 }

操作4: 格式:x表示求以x為根節點的子樹內所有節點值之和

1 int getSON(int x){
2     return getsum(id[x],id[x]+size[x]-1,1,n,1);//這個也還是一樣的 
3 }

線段樹操作

其實和板子差不多,就是由幾個小細節

比如存節點資訊的時候要記得是剖分後的陣列,還有一些細節的地方比如左右區間啊

  1 #include<bits/stdc++.h>
  2 #define int long long
  3 #define ls(p) (p<<1)
  4 #define rs(p) (p<<1|1)
  5 using namespace std;
  6 const int N=100010;
  7 const int M=500010;
  8 int n,m,r,mod;
  9 int cnt;
 10 int a[N],last[N];
 11 int son[N],deep[N],size[N],fa[N];
 12 int id[N],top[N],v[N];
 13 int ans[4*N],tag[4*N];//線段樹要開大點 
 14 struct node{
 15     int pre,to;
 16 }e[2*M];
 17 inline int read(){
 18     int x=0,f=1;
 19     char ch=getchar();
 20     while(ch<'0'||ch>'9'){
 21         if(ch=='-')
 22             f=-1;
 23         ch=getchar();
 24     }
 25     while(ch>='0'&&ch<='9'){
 26         x=(x<<1)+(x<<3)+(ch^48);
 27         ch=getchar();
 28     }
 29     return x*f;
 30 }
 31 void add(int x,int y){//鏈式前向星 
 32     cnt++;
 33     e[cnt].pre=last[x];
 34     e[cnt].to=y;
 35     last[x]=cnt;
 36 }
 37 //-----------------------------------------線段樹 
 38 void push_up(int p){
 39     ans[p]=ans[ls(p)]+ans[rs(p)];//維護節點 
 40 }
 41 inline void f(int p,int l,int r,int k){//向下傳遞 
 42     tag[p]+=k;
 43     ans[p]+=k*(r-l+1);
 44     return;
 45 }
 46 void push_down(int p,int l,int r){//向下傳遞 
 47     int mid=(l+r)>>1;
 48     f(ls(p),l,mid,tag[p]);
 49     f(rs(p),mid+1,r,tag[p]);
 50     tag[p]=0;
 51     return;
 52 }
 53 void ADD(int nl,int nr,int l,int r,int p,int k){//要操作的左右區間和當前左右區間和編號和要變化的資訊 
 54     if(nl<=l&&r<=nr)
 55     {
 56         ans[p]+=k*(r-l+1);
 57         tag[p]+=k;
 58         return;
 59     }
 60     push_down(p,l,r);
 61     int mid=(l+r)>>1;
 62     if(mid>=nl)ADD(nl,nr,l,mid,ls(p),k);
 63     if(mid<nr)ADD(nl,nr,mid+1,r,rs(p),k);
 64     push_up(p);
 65 }
 66 void build(int p,int l,int r){//當前點和左右區間 
 67     if(l==r)
 68     {
 69         ans[p]=v[l];//一定是剖分後的陣列 
 70         return;
 71     }
 72     int mid=(l+r)>>1;
 73     build(ls(p),l,mid);
 74     build(rs(p),mid+1,r);
 75     push_up(p);
 76     return ;
 77 }
 78 int getsum(int nl,int nr,int l,int r,int p){
 79     if(nl<=l&&r<=nr)
 80         return ans[p];
 81     push_down(p,l,r);
 82     int mid=(l+r)>>1,res=0;
 83     if(mid>=nl)res+=getsum(nl,nr,l,mid,ls(p));
 84     if(mid<nr)res+=getsum(nl,nr,mid+1,r,rs(p));
 85     return res;
 86 }
 87 //---------------------------------樹鏈剖分 
 88 void dfs1(int x,int f){
 89     size[x]=1;
 90     fa[x]=f;
 91     for(int i=last[x];i;i=e[i].pre)
 92     {
 93         int y=e[i].to;
 94         if(y==f)continue;
 95         deep[y]=deep[x]+1;
 96         dfs1(y,x);
 97         size[x]+=size[y];
 98         if(size[y]>size[son[x]])son[x]=y;
 99     }
100 }
101 void dfs2(int x,int topf){
102     id[x]=++cnt;
103     top[x]=topf;
104     v[cnt]=a[x];
105     if(!son[x])return;
106     dfs2(son[x],topf);
107     for(int i=last[x];i;i=e[i].pre)
108     {
109         int y=e[i].to;
110         if(y==fa[x]||y==son[x])continue;
111         dfs2(y,y);
112     }
113 }
114 void upG(int x,int y,int k){
115     while(top[x]!=top[y])
116     {
117         if(deep[top[x]]<deep[top[y]])swap(x,y);
118         ADD(id[top[x]],id[x],1,n,1,k);
119         x=fa[top[x]];
120     }
121     if(deep[x]<deep[y])swap(x,y);
122     ADD(id[y],id[x],1,n,1,k);
123 }
124 void upSON(int x,int k){
125     ADD(id[x],id[x]+size[x]-1,1,n,1,k);
126 }
127 int getG(int x,int y){
128     int res=0;
129     while(top[x]!=top[y])
130     {
131         if(deep[top[x]]<deep[top[y]])swap(x,y);
132         res+=getsum(id[top[x]],id[x],1,n,1);
133         x=fa[top[x]];
134     }
135     if(deep[x]<deep[y])swap(x,y);
136     res+=getsum(id[y],id[x],1,n,1);
137     return res;
138 }
139 int getSON(int x){
140     return getsum(id[x],id[x]+size[x]-1,1,n,1); 
141 }
142 //---------------------------------
143 
144 signed main()
145 {
146     n=read(),m=read(),r=read(),mod=read();
147     for(int i=1;i<=n;i++)a[i]=read();
148     for(int i=1;i<n;i++)
149     {
150         int x,y;
151         x=read(),y=read();
152         add(x,y),add(y,x);
153     }
154     cnt=0;
155     dfs1(r,0);
156     dfs2(r,r);
157     build(1,1,n);
158     while(m--)
159     {
160         int k,x,y,z;
161         k=read();
162         switch(k)
163         {
164             case 1:{
165                 x=read(),y=read(),z=read();
166                 upG(x,y,z);
167                 break;
168             }
169             case 2:{
170                 x=read(),y=read();
171                 printf("%lld\n",getG(x,y)%mod);
172                 break;
173             }
174             case 3:{
175                 x=read(),y=read();
176                 upSON(x,y);
177                 break;
178             }
179             case 4:{
180                 x=read();
181                 printf("%lld\n",getSON(x)%mod);
182                 break;
183             }
184         }
185     }
186     return 0;
187 }

(注意我的define,不開long long 見祖宗)

理解不是很難,但是出錯率很高,要經常訓練才行