1. 程式人生 > >學習筆記:樹鏈剖分

學習筆記:樹鏈剖分

ostream head bubuko tdi OS 題目 add uil 如何實現

現在的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 }

學習筆記:樹鏈剖分