首先,樹(shù)鏈剖分是個(gè)什么東西?
給出一道例題:維護(hù)兩個(gè)操作:
1,修改單點(diǎn)值;2,求x~y路徑的權(quán)值和;
點(diǎn)數(shù)<=100000,操作數(shù)量<=100000.
考慮LCA?不行,LCA不能支持修改,極限復(fù)雜度(N*N),于是…
接下來(lái),引出數(shù)據(jù)結(jié)構(gòu)-----樹(shù)鏈剖分。
定義與概念:(劃重點(diǎn))
重兒子:父親節(jié)點(diǎn)的所有兒子中子樹(shù)結(jié)點(diǎn)數(shù)目最多(size最大)的結(jié)點(diǎn);
輕兒子:父親節(jié)點(diǎn)中除了重兒子以外的兒子;
重邊:父親結(jié)點(diǎn)和重兒子連成的邊;
輕邊:父親節(jié)點(diǎn)和輕兒子連成的邊;
重鏈:由多條重邊連接而成的路徑;
輕鏈:由多條輕邊連接而成的路徑;
請(qǐng)花至少五分鐘理解之,上圖:
進(jìn)行變量聲明:
size[v]代表以v為根的子樹(shù)的節(jié)點(diǎn)數(shù)
dep[v]代表v的深度(根深度為1)
top[v]代表v所在的鏈頂頂端節(jié)點(diǎn)的編號(hào)
fa[v]代表v的父親,son[v]代表v的重兒子。
tree[v]代表節(jié)點(diǎn)v在線(xiàn)段樹(shù)(數(shù)據(jù)結(jié)構(gòu))中的編號(hào)(dfs序)
pre[v]代表線(xiàn)段樹(shù)(數(shù)據(jù)結(jié)構(gòu))中編號(hào)為v的節(jié)點(diǎn)所對(duì)應(yīng)的原圖中的點(diǎn)編號(hào)(tree[v]與pre[v]是互逆的,tree[2]=3,則pre[3]=2.)
開(kāi)始樹(shù)鏈剖分!
Top1:dg1
此遞歸可以一舉求出size,dep,fa與son。
其中dep[f[k][1]]=dep[t]+1,fa[f[k][1]]=t,size[t]+=size[f[k][1]];
son[t]只需通過(guò)比較size[f[k][1]]即可得出。(f[k][1]是t的一個(gè)兒子)
注意:
如果有多個(gè)最大size,任選一個(gè);
葉節(jié)點(diǎn)無(wú)重兒子,非葉節(jié)點(diǎn)有且只有一個(gè)重兒子
標(biāo)程:
void dg1(int t)
{size[t]=1;//點(diǎn)本身size=1;for (int k=q[t];k;k=ff[k][2]) {int h=ff[k][1];//ff[k][1]是前向星中t的一個(gè)兒子if (h!=fa[t]) {fa[h]=t,dep[h]=dep[t]+1;//求fa與depdg1(h);size[t]+=size[h];//求sizeif ((!son[t])||(size[son[t]]<size[h])) son[t]=h;//求son,選擇兒子中最大的size}}
}
//dg1跑完上圖后如此:
size 14 5 2 6 1 3 1 1 3 1 1 1 2 1
son 4 6 7 9 0 11 0 0 13 0 0 0 0 0fa 0 1 1 1 2 2 3 4 4 4 6 6 9 13
dep 1 2 2 3 3 3 3 3 3 4 4 4 4 4
Top2:dg2
求出tree,pre與top。
為了用數(shù)據(jù)結(jié)構(gòu)維護(hù)重鏈,我們要讓重鏈的DFS序連續(xù)
標(biāo)程:
void dg2(int t,int ad)
{tot++;tree[t]=tot,pre[tot]=t,top[t]=ad;//求tree與pre,tree[x]=y則pre[y]=x;if (!son[t]) return;dg2(son[t],ad);//優(yōu)先遞歸重兒子,讓重鏈DFS序連續(xù)for (int k=q[t];k;k=ff[k][2]) {int h=ff[k][1];//dg1中已解釋if ((h!=fa[t])&&(h!=son[t])) dg2(h,h);//如果一個(gè)點(diǎn)位于輕鏈頂端,則它的鏈頂節(jié)點(diǎn)為本身}
}
//dg2跑完上圖后如此
top 1 2 3 1 5 2 7 8 1 10 2 12 1 1
tree 1 10 8 2 14 11 9 6 3 7 12 13 4 5
pre 1 4 9 13 14 8 10 3 7 2 6 11 12 5
好了,樹(shù)鏈剖分結(jié)束了。
WTF?這就結(jié)束了?說(shuō)好150+行的代碼呢?
是的,樹(shù)鏈剖分的主要操作已經(jīng)結(jié)束,我們將樹(shù)轉(zhuǎn)換成了一條條的鏈加以維護(hù)。
不過(guò),還有一個(gè)遺留難題:為什么樹(shù)鏈剖分的時(shí)間復(fù)雜度是O(nlog^2n)?
有兩條性質(zhì):
1.若(u,v)為輕邊(u是v父親),則size[u]>size[v]*2。
這個(gè)顯然,如果不滿(mǎn)足size[u]>size[v]*2,size[v]必然不小于其它兒子的size,那么(u,v)不該是輕邊。
2.從根結(jié)點(diǎn)到任意結(jié)點(diǎn)的路所經(jīng)過(guò)的輕重鏈的個(gè)數(shù)必定都小與O(logn);
這個(gè)不顯然,但易證。
先證輕鏈:因?yàn)?性質(zhì),如果(u,v)為輕邊,從u到v的size至少減一半;
那么最多只能經(jīng)過(guò)log(n)層。
重鏈與輕鏈?zhǔn)遣⑿械?#xff0c;所以重鏈最多有l(wèi)og(n)+1條
證畢。
對(duì)于這些鏈,用線(xiàn)段樹(shù)維護(hù)即可。
模板題:JZOJ2256 ZJOI2008 樹(shù)的統(tǒng)計(jì)
#include <cstdio>
#include <algorithm>
#include <cstdlib>
#include <cstring>
using namespace std;
int i,j,k,m,n,o,p,l,s,t,x,y,tot;
struct node{int sum,max,l,r;}f[1200001];
int ff[600001][3],q[600001],fa[300001],son[300001],dep[300001],size[300001];
int top[300001],tree[300001],pre[300001],a[300001];
char ch[7];
void insert(int x,int y)
{ t++,ff[t][1]=y,ff[t][2]=q[x],q[x]=t; }
void downdata(int x)
{int xx=x*2,yy=x*2+1;f[x].sum=f[xx].sum+f[yy].sum;f[x].max=max(f[xx].max,f[yy].max);
}
void change(int x,int v,int ad)
{if (f[x].l==f[x].r) f[x].max=ad,f[x].sum=ad;else {int mid=(f[x].l+f[x].r)/2;if (v<=mid) change(x*2,v,ad); else change(x*2+1,v,ad);downdata(x);}
}
void make(int x,int l,int r)
{f[x].l=l,f[x].r=r;if (l==r) f[x].sum=f[x].max=a[pre[l]];else {int mid=(l+r)/2;make(x*2,l,mid);make(x*2+1,mid+1,r);downdata(x);}
}
void dg1(int t)
{size[t]=1;for (int k=q[t];k;k=ff[k][2]) {int h=ff[k][1];if (h!=fa[t]) {fa[h]=t,dep[h]=dep[t]+1;dg1(h);size[t]+=size[h];if ((!son[t])||(size[son[t]]<size[h])) son[t]=h;}}
}
void dg2(int t,int ad)
{tot++;tree[t]=tot,pre[tot]=t,top[t]=ad;if (!son[t]) return;dg2(son[t],ad);for (int k=q[t];k;k=ff[k][2]) {int h=ff[k][1];if ((h!=fa[t])&&(h!=son[t])) dg2(h,h);}
}
int getsum(int t,int x,int y)
{if ((f[t].l>=x)&&(f[t].r<=y)) return f[t].sum;int mid=(f[t].l+f[t].r)/2,sum=0;if (x<=mid) sum+=getsum(t*2,x,y);if (y>mid) sum+=getsum(t*2+1,x,y);return sum;
}
int getmax(int t,int x,int y)
{if ((f[t].l>=x)&&(f[t].r<=y)) return f[t].max;int mid=(f[t].l+f[t].r)/2,Max=-2147483648;if (x<=mid) Max=max(Max,getmax(t*2,x,y));if (y>mid) Max=max(Max,getmax(t*2+1,x,y));return Max;
}
int findmax(int x,int y)
{int xx=top[x],yy=top[y],maxans=-2147483648;while (xx!=yy) {if (dep[xx]<dep[yy]) swap(xx,yy),swap(x,y);maxans=max(maxans,getmax(1,tree[xx],tree[x]));x=fa[xx],xx=top[x];} if (dep[x]>dep[y]) swap(x,y);maxans=max(maxans,getmax(1,tree[x],tree[y]));return maxans;
}
int findsum(int x,int y)
{int xx=top[x],yy=top[y],sumans=0;while (xx!=yy) {if (dep[xx]<dep[yy]) swap(xx,yy),swap(x,y);sumans+=getsum(1,tree[xx],tree[x]);x=fa[xx],xx=top[x];} if (dep[x]>dep[y]) swap(x,y);sumans+=getsum(1,tree[x],tree[y]);return sumans;
}
int main()
{scanf("%d",&n);for (i=1;i<=n-1;i++) {scanf("%d%d",&x,&y);insert(x,y);insert(y,x);}for (i=1;i<=n;i++) scanf("%d",&a[i]);t=0;dep[1]=1;dg1(1);dg2(1,1);make(1,1,n);scanf("%d",&m);for (i=1;i<=m;i++) {scanf("%s",&ch);scanf("%d%d",&x,&y);if (ch[1]=='H') {change(1,tree[x],y);} else {printf("%d\n",ch[1]=='M'?findmax(x,y):findsum(x,y));}}
}
總結(jié)
以上是生活随笔為你收集整理的树链剖分解析---WYD的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。