【毒鏈剖分】
阿新 • • 發佈:2020-08-06
樹鏈剖分
這是個讓初學者望而卻步的東西,不管打了多少次,也很難一遍過(我太弱了)
根據這個樹鋸結構可知,這是個鋸樹結構。它把一棵樹拆分成若干條鏈,從而利用一些其他的資料結構來維護每一條鏈
常見的路徑剖分的方法是輕重樹鏈剖分(啟發式剖分)
那我們先來康康毒鏈剖分有哪些操作吧!
定義
在輕重鏈剖分中,對於一個節點,他的一顆以兒子為根節點,節點數最多的子樹稱為重兒子,其連邊稱為重邊,其餘稱為輕邊,重邊組成的鏈叫做重鏈
所以第一次我們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 見祖宗)
理解不是很難,但是出錯率很高,要經常訓練才行