學習筆記:樹鏈剖分
現在的noip好像難度一年比一年高了啊……去年(2016)的noip竟然考了一道鏈剖……如果我當時在考場上怕不是要直接GG……所以嚇得我趕緊來學鏈剖了(雖然學了估計我考場上也寫不出)。
什麽是樹鏈剖分?
樹鏈剖分……如果你不會線段樹的話……還是就此打住吧。
樹鏈剖分,顧名思義,就是將一顆樹分解成一條條鏈(廢話)
然後將這一條條鏈收尾相接連成一條鏈,用線段樹進行查詢和修改
通常用來處理在一顆形狀恒不變的樹上,修改邊/點權,查詢某個值的題目。
沒了……(詞窮)
如何實現樹鏈剖分?
先引入幾個概念
首先定義:
Weight[u]保存u節點的子樹大小
Father[u]保存u節點的父親節點
Son[u]表示u節點的重兒子節點
Top[u]表示u節點的重鏈的頂端節點
T_NUM[u]表示u與其父親的連邊在線段樹中的編號
TREE[u]線段樹區間中下標為u的點權
也許到這就看不懂了?沒事,抱著疑問繼續往下看
重兒子:Weight[u]為 若v為u的的子節點中子樹最大的,那麽v就是u的重兒子。
輕兒子:v的其它子節點。
重邊:點v與其重兒子的連邊。
輕邊:點v與其輕兒子的連邊。
重鏈:由重邊連成的路徑。
輕鏈:輕邊。
……
……
……
……
……
……
……
……
……
算了我好像都沒給自己說懂……我還是舉個例子吧
舉個栗子
下圖中粗的為重鏈(如1-4-9-13-14),細的為輕鏈,帶紅點的為重鏈的頂端結點(Top),每個邊表示了它在線段樹區間的坐標(T_NUM),下標為T_NUM[x]的線段樹區間存x的點/邊權
到這應該數組概念都懂了吧?
唯一的疑惑是,T_NUM是按怎麽算的?這裏為了保證重鏈區間的連續性,所以我們dfs遍歷的時候優先走重鏈,那樣就可以保證重鏈在線段樹區間中是一段連續的區間啦。
修改
不妨想想,我們為什麽要用線段樹呢?
因為線段樹可以很方便的對區間(點)進行修改和查詢!
所以如果修改點的話……這玩意兒直接修改就好了啊……
但修改區間呢?這個和下面的查詢是類似的,不過查詢是一邊跳一邊query,修改是一邊跳一邊update……往下看就知道了
別告訴我你不會線段樹……開頭我提醒過的emm……
(不會的你還是去面壁吧……)
查詢
1.查詢路徑
我們不妨先考慮如果暴力在一顆動態的樹上查詢,我們應該怎麽做?
當然是找LCA然後一步步查詢了!不過一步步往上跳顯然太慢了。
所以輕重鏈的作用其實就是為了讓我們跳的更快!
如何跳的更快呢?
我們模仿LCA,當兩個點不在同一條重鏈上時,每次讓兩個點中Top更深的往上跳,如果當前要跳的點在重鏈上的話,恭喜你,你的點可以直接跳到這條重鏈的頂端了!中途的過程我們就用線段樹的區間查詢好了。畢竟建線段樹的時候我們保證了重鏈上的點在線段樹的區間中是連續的。
直到兩個點跳到同一條重鏈上。我們再模仿LCA最後一起跳一步的方法,最後我們只需要查詢兩個點在當前重鏈之間的信息就好了。
這樣就可以快很多了。
例如:
當要修改11到10的路徑時。
第一次叠代:u = 11,v = 10,f1 = 2,f2 =
10。此時dep[f1] < dep[f2],因此 修改線段樹中的5號點,v = 4, f2 = 1;
第二次叠代:dep[f1] > dep[f2],修改線段樹中10--11號點。u = 2,f1 =
2;
第三次叠代:dep[f1] > dep[f2],修改線段樹中9號點。u = 1,f1 = 1;
第四次叠代:f1 = f2且u = v,修改結束。
2.查詢子樹(補)
這個其實很簡單的……觀察上面的圖我們可以發現某個節點的子樹中的每個點/邊在線段樹區間中一定是連續的……所以我們直接區間查詢就好了……修改也是同理……
不多說了,貼代碼(單點修改,查詢路徑最大值或長度BZOJ1036)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstdlib> 4 #include<cstring> 5 using namespace std; 6 7 int head[30001],num_edge; 8 int Father[30001],Weight[30001]; 9 int Son[30001],Top[30001]; 10 int T_NUM[30001],a[30001]; 11 int Depth[30001]; 12 int sum,n,INF,ans; 13 int TREE[30001]; 14 string st; 15 struct node 16 { 17 int to,next; 18 } edge[60005]; 19 struct node1 20 { 21 int max,sum; 22 }Segt[120001]; 23 void add(int u,int v) 24 { 25 edge[++num_edge].to=v; 26 edge[num_edge].next=head[u]; 27 head[u]=num_edge; 28 } 29 void dfs1(int x)//第一遍bfs處理Son,Weight,Depth幾個數組 30 { 31 Weight[x]=1; 32 Depth[x]=Depth[Father[x]]+1; 33 for (int i=head[x]; i!=0; i=edge[i].next) 34 if (edge[i].to!=Father[x]) 35 { 36 Father[edge[i].to]=x; 37 dfs1(edge[i].to); 38 Weight[x]+=Weight[edge[i].to]; 39 if (Son[x]==0 || Weight[edge[i].to]>Weight[Son[x]]) 40 Son[x]=edge[i].to; 41 } 42 } 43 44 void dfs2(int x,int tp)//第二次就優先dfs重兒子,建立線段樹 45 { 46 T_NUM[x]=++sum; 47 TREE[sum]=a[x]; 48 Top[x]=tp; 49 if (Son[x]!=0) 50 dfs2(Son[x],tp); 51 for (int i=head[x]; i!=0; i=edge[i].next) 52 if (edge[i].to!=Son[x] && edge[i].to!=Father[x]) 53 dfs2(edge[i].to,edge[i].to); 54 } 55 56 void Build(int node,int l,int r,int a[])//這個和下面的Update都是線段樹板子…… 57 { 58 if (l==r) 59 Segt[node].max=Segt[node].sum=a[l]; 60 else 61 { 62 int mid=(l+r)/2; 63 Build(node*2,l,mid,a); 64 Build(node*2+1,mid+1,r,a); 65 Segt[node].max=max(Segt[node*2].max,Segt[node*2+1].max); 66 Segt[node].sum=Segt[node*2].sum+Segt[node*2+1].sum; 67 } 68 } 69 70 void Update(int node,int l,int r,int x,int k) 71 { 72 if (l==r) 73 Segt[node].max=Segt[node].sum=k; 74 else 75 { 76 int mid=(l+r)/2; 77 if (x<=mid) 78 Update(node*2,l,mid,x,k); 79 else 80 Update(node*2+1,mid+1,r,x,k); 81 Segt[node].max=max(Segt[node*2].max,Segt[node*2+1].max); 82 Segt[node].sum=Segt[node*2].sum+Segt[node*2+1].sum; 83 } 84 } 85 86 int QueryMax(int node,int l,int r,int l1,int r1)//查詢區間最大值,線段樹模板 87 { 88 if (r<l1 || l>r1) 89 return -INF; 90 if (l1<=l && r<=r1) 91 return Segt[node].max; 92 int mid=(l+r)/2; 93 return max(QueryMax(node*2,l,mid,l1,r1), 94 QueryMax(node*2+1,mid+1,r,l1,r1)); 95 } 96 97 int QuerySum(int node,int l,int r,int l1,int r1)//查詢區間和,線段樹模板 98 { 99 if (r<l1 || l>r1) 100 return 0; 101 if (l1<=l && r<=r1) 102 return Segt[node].sum; 103 int mid=(l+r)/2; 104 return QuerySum(node*2,l,mid,l1,r1)+ 105 QuerySum(node*2+1,mid+1,r,l1,r1); 106 } 107 108 int GetSum(int x,int y)//求x和y點之間的路徑長度和 109 { 110 int fx,fy; 111 memset(&ans,0,sizeof(ans)); 112 fx=Top[x];fy=Top[y]; 113 while (fx!=fy) 114 { 115 if (Depth[fx]<Depth[fy]) 116 swap(fx,fy),swap(x,y); 117 ans+=QuerySum(1,1,n,T_NUM[fx],T_NUM[x]); 118 x=Father[fx],fx=Top[x]; 119 } 120 if (Depth[x]>Depth[y]) swap(x,y); 121 return ans+=QuerySum(1,1,n,T_NUM[x],T_NUM[y]); 122 } 123 124 int GetMax(int x,int y//求x和y點之間的最大一條路徑長度 125 { 126 int fy,fx; 127 memset(&ans,-0x7f,sizeof(ans)); 128 fx=Top[x];fy=Top[y]; 129 while (fx!=fy) 130 { 131 if (Depth[fx]<Depth[fy]) 132 swap(fx,fy),swap(x,y); 133 ans=max(ans,QueryMax(1,1,n,T_NUM[fx],T_NUM[x])); 134 x=Father[fx],fx=Top[x]; 135 } 136 if (Depth[x]>Depth[y]) swap(x,y); 137 return max(ans,QueryMax(1,1,n,T_NUM[x],T_NUM[y])); 138 } 139 140 int main() 141 { 142 ios::sync_with_stdio(false); 143 int i,u,v,l,m,x,y; 144 memset(&INF,0x7f,sizeof(INF)); 145 cin>>n; 146 for (i=1; i<=n-1; ++i) 147 { 148 cin>>u>>v; 149 add(u,v); 150 add(v,u); 151 } 152 for (int i=1;i<=n;++i) 153 cin>>a[i]; 154 Depth[1]=1; 155 dfs1(1); 156 dfs2(1,1);//兩邊預處理 157 Build(1,1,n,TREE);//TREE數組保存用來建線段樹的區間 158 cin>>m; 159 for (int i=1;i<=m;++i) 160 { 161 cin>>st>>x>>y; 162 if (st=="CHANGE") 163 Update(1,1,n,T_NUM[x],y); 164 if (st=="QMAX") 165 cout<<GetMax(x,y)<<endl; 166 if (st=="QSUM") 167 cout<<GetSum(x,y)<<endl; 168 } 169 }
學習筆記:樹鏈剖分