重鏈剖分

樹鏈剖分,是一種能夠把一棵有根樹劃分紅許多條鏈,從而簡單地實現樹上修改與查詢操做的 算法/數據結構(我也不知道屬於哪一個QwQ)。
固然這裏的樹鏈剖分是指重鏈剖分。算法

先放模板:P3384 【模板】輕重鏈剖分
(嚶嚶嚶她藍了)數據結構

學習重鏈剖分,你首先要知道如下名詞:學習

重兒子: 對於一個非葉子節點u,有許多子節點。而其中有一個子節點v,以他爲根的子樹的大小比其餘子節點都大,那麼v就是u的重兒子。
輕兒子: 不是重兒子就是輕兒子。
重鏈: 一條除了頂部是輕兒子,其餘都是重兒子的路徑。ui

光是文字好像不容易搞懂,那麼來看看這張圖:

重兒子和重鏈已經用紅色標出來了。spa

接下來就是樹剖的實現啦!code

樹剖實際上就是兩遍預處理。第一遍咱們要求出每一個點的父節點深度子樹大小重兒子,分別記爲fa,depth,size,son
其餘三個都是信手拈來,而這個重兒子嘛……就是求子樹最大的那個點啦!因而代碼就很天然得寫出來了:blog

void dfs1(int now,int F)
{
	int k=0;//k用來記錄當前找到的子樹最大的點的子樹大小
	fa[now]=F,size[now]=1;
	depth[now]=depth[F]+1;
    	//你們都熟悉的求父節點和深度
	for(int i=g.hd[now];i;i=g.nxt[i])
		if(g.to[i]!=F)//首先不能是父節點
		{
			dfs1(g.to[i],now);//遞歸
			size[now]+=size[g.to[i]];//依然很熟悉的求子樹大小
			if(size[g.to[i]]>k)//這棵子樹的大小比以前的更大呢
			{
				k=size[g.to[i]];
				son[now]=g.to[i];
                		//那麼就更新
			}
		}
	return ;
}

第二遍咱們要求每一個點所在的重鏈的頂端第幾個被遍歷,分別計爲topid
那麼這個怎麼求呢?很簡單,咱們不用記錄父節點了(上面已經求出來了),而是記錄 這個點所在的重鏈的頂端 。這樣就能夠解決top了:遞歸

void dfs2(int now,int F)//這裏的F是now所在的重鏈的頂端
{
	top[now]=F;//記錄top
	id[now]=++cnt;//記錄id
	wt[id[now]]=a[now];//這裏等下再解釋
	if(son[now]==0) return ;//重兒子是0,就是沒有重兒子,那麼就是葉子節點,結束。
	dfs2(son[now],F);//要先遞歸重兒子哦
	for(int i=g.hd[now];i;i=g.nxt[i])
		if(g.to[i]!=fa[now]&&g.to[i]!=son[now])//是輕兒子
			dfs2(g.to[i],g.to[i]);//遞歸
        return ;
}

而後你就學會樹剖了(逃
而後你就要解決修改和查詢了。
在這以前,先觀察一下id,能夠發如今一條重鏈上,id是連續的。在一棵子樹裏也是。
而後咱們就須要這個性質來解決修改和查詢了。get

先看第一個操做:io

將樹從x到y結點最短路徑上全部節點的值都加上z

回想一下倍增LCA怎麼搞?深度大的往上跳,直到父親相同。這裏也是同樣。因爲一條重鏈上id連續,因此每條重鏈求和只要讓id[top[u]]id[u]都加上k就OK了。而這個操做可讓線段樹來完成。這也是爲何我以前在第二遍\(dfs\)的時候要wt[id[now]]=a[now];。線段樹建樹的時候就是把wt的值賦上去的。
以後,再讓u跳到fa[top[u]]便可:

void add_path(int x,int y,int k)
{
	while(top[x]!=top[y])//不在同一條重鏈上
	{
		if(depth[top[x]]<depth[top[y]]) swap(x,y);//讓x是深度大的那個
		tr.change(id[top[x]],id[x],1,k);//區間修改
		x=fa[top[x]];//往上跳
	}
	if(depth[x]>depth[y]) swap(x,y);//這裏在同一條重鏈上,要讓x作深度小的那個了
	tr.change(id[x],id[y],1,k);//修改他們中間的那段
	return ;
}

操做2也同樣,只不過把修改改爲了查詢:

int query_path(int x,int y)
{
	int res=0;
	while(top[x]!=top[y])
	{
		if(depth[top[x]]<depth[top[y]]) swap(x,y);
		res=(res+tr.ask(id[top[x]],id[x],1))%Mod;//記得取模
		x=fa[top[x]];
	}
	if(depth[x]>depth[y]) swap(x,y);
	res=(res+tr.ask(id[x],id[y],1))%Mod;//這裏也是
	return res;
}

而後是操做3:。

將以x爲根節點的子樹內全部節點值都加上z。

因爲子樹內的id連續,因此最小的那個是id[u],共有size[u]個,因此修改的區間就是id[u]id[u]+size[u]-1

void add_son(int x,int k)
{
	tr.change(id[x],id[x]+size[x]-1,1,k);
	return ;
}

操做4也同樣:

int query_son(int x)
{
	return tr.ask(id[x],id[x]+size[x]-1,1);
}

好了,這題就結束了!總複雜度\(O(nlog^2n)\)
總體代碼長這個亞子:

#include<cstdio>
#define MAXN 100005
#define int long long
using namespace std;
int n,m,Root,Mod,cnt;
int a[MAXN],wt[MAXN];
int fa[MAXN],size[MAXN],depth[MAXN];
int son[MAXN],top[MAXN],id[MAXN];
void swap(int &x,int &y)
{
	int t=x;
	x=y;
	y=t;
	return ;
}
struct graph
{
	int tot,hd[MAXN];
	int nxt[MAXN*2],to[MAXN*2];
	void add(int u,int v)
	{
		tot++;
		nxt[tot]=hd[u];
		hd[u]=tot;
		to[tot]=v;
		return ;
	}
}g;
struct Tree
{
	int w[MAXN*4],l[MAXN*4],r[MAXN*4];
	int f[MAXN*4];
	void build(int ll,int rr,int k)
	{
		l[k]=ll,r[k]=rr;
		if(ll==rr)
		{
			w[k]=wt[ll]%Mod;
			return ;
		}
		int mid=(ll+rr)/2;
		build(ll,mid,k*2);
		build(mid+1,rr,k*2+1);
		w[k]=(w[k*2]+w[k*2+1])%Mod;
		return ;
	}
	void down(int k)
	{
		f[k*2]=(f[k*2]+f[k])%Mod;
		f[k*2+1]=(f[k*2+1]+f[k])%Mod;
		w[k*2]=(w[k*2]+f[k]*(r[k*2]-l[k*2]+1)%Mod)%Mod;
		w[k*2+1]=(w[k*2+1]+f[k]*(r[k*2+1]-l[k*2+1]+1)%Mod)%Mod;
		f[k]=0;
		return ;
	}
	void change(int ll,int rr,int k,int x)
	{
		if(l[k]>=ll&&r[k]<=rr)
		{
			w[k]=(w[k]+x*(r[k]-l[k]+1)%Mod)%Mod;
			f[k]=(f[k]+x)%Mod;
			return ;
		}
		if(f[k]) down(k);
		int mid=(l[k]+r[k])/2;
		if(ll<=mid) change(ll,rr,k*2,x);
		if(rr>mid) change(ll,rr,k*2+1,x);
		w[k]=w[k*2]+w[k*2+1];
		return ;
	}
	int ask(int ll,int rr,int k)
	{
		if(l[k]>=ll&&r[k]<=rr) return w[k]%Mod;
		if(f[k]) down(k);
		int res=0,mid=(l[k]+r[k])/2;
		if(ll<=mid) res=(res+ask(ll,rr,k*2))%Mod;
		if(rr>mid) res=(res+ask(ll,rr,k*2+1))%Mod;
		return res;
	}
}tr;
void dfs1(int now,int F)
{
	int k=0;
	fa[now]=F,size[now]=1;
	depth[now]=depth[F]+1;
	for(int i=g.hd[now];i;i=g.nxt[i])
		if(g.to[i]!=F)
		{
			dfs1(g.to[i],now);
			size[now]+=size[g.to[i]];
			if(size[g.to[i]]>k)
			{
				k=size[g.to[i]];
				son[now]=g.to[i];
			}
		}
	return ;
}
void dfs2(int now,int F)
{
	top[now]=F;
	id[now]=++cnt;
	wt[id[now]]=a[now];
	if(son[now]==0) return ;
	dfs2(son[now],F);
	for(int i=g.hd[now];i;i=g.nxt[i])
		if(g.to[i]!=fa[now]&&g.to[i]!=son[now])
			dfs2(g.to[i],g.to[i]);
	return ;
}
void add_path(int x,int y,int k)
{
	while(top[x]!=top[y])
	{
		if(depth[top[x]]<depth[top[y]]) swap(x,y);
		tr.change(id[top[x]],id[x],1,k);
		x=fa[top[x]];
	}
	if(depth[x]>depth[y]) swap(x,y);
	tr.change(id[x],id[y],1,k);
	return ;
}
int query_path(int x,int y)
{
	int res=0;
	while(top[x]!=top[y])
	{
		if(depth[top[x]]<depth[top[y]]) swap(x,y);
		res=(res+tr.ask(id[top[x]],id[x],1))%Mod;
		x=fa[top[x]];
	}
	if(depth[x]>depth[y]) swap(x,y);
	res=(res+tr.ask(id[x],id[y],1))%Mod;
	return res;
}
void add_son(int x,int k)
{
	tr.change(id[x],id[x]+size[x]-1,1,k);
	return ;
}
int query_son(int x)
{
	return tr.ask(id[x],id[x]+size[x]-1,1);
}
signed main()
{
	scanf("%lld%lld%lld%lld",&n,&m,&Root,&Mod);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	for(int i=1;i<n;i++)
	{
		int u,v;
		scanf("%lld%lld",&u,&v);
		g.add(u,v);
		g.add(v,u);
	}
	dfs1(Root,0);
	dfs2(Root,Root);
	tr.build(1,n,1);
	for(int i=1;i<=m;i++)
	{
		int opt;
		scanf("%lld",&opt);
		if(opt==1)
		{
			int x,y,k;
			scanf("%lld%lld%lld",&x,&y,&k);
			add_path(x,y,k);
		}
		else if(opt==2)
		{
			int x,y;
			scanf("%lld%lld",&x,&y);
			printf("%lld\n",query_path(x,y));
		}
		else if(opt==3)
		{
			int x,k;
			scanf("%lld%lld",&x,&k);
			add_son(x,k);
		}
		else
		{
			int x;
			scanf("%lld",&x);
			printf("%lld\n",query_son(x));
		}
	}
	return 0;
}

固然,樹剖也能用來求\(LCA\),怎麼求的話……看看上面的操做1操做2就能明白吧。

相關文章
相關標籤/搜索