『笔记』树链剖分 轻重链剖分

前置知识

  • 最近公共祖先(LCA),树形DP,DFS序,链式前向星存图,线段树

功能

对一棵树进行剖分,将其分成几条链,将树形变为线性,减少处理的难度
需要处理的问题有

  • 将树从\(x\)\(y\)结点最短路径上所有节点的值都加上\(z\)
  • 求树从\(x\)\(y\)结点最短路径上所有节点的值之和
  • 将以\(x\)为根节点的子树内所有节点值都加上\(z\)
  • 求以\(x\)为根节点的子树内所有节点值之和

定义及概念

  • 重儿子:对于每一个非叶子节点,他的儿子中以那个儿子为根的子树的节点数最大的儿子,为该点的重儿子
  • 轻儿子:对于每个非叶子节点,他的儿子中不是重儿子的剩下的所有儿子就是轻儿子
  • 叶子节点没有重儿子也没有轻儿子,因为他根本没有儿子(QAQ)
  • 重边:一个父亲连接他的重儿子的边称为重边
  • 轻边:重边剩下的就是轻边
  • 重链:相邻的重边连起来的连接一条重儿子的链称为重链
    • 对于叶子节点,如果他是轻儿子,那么有一条以他自己为起点的长度为1的链
    • 每一条重链以其轻儿子为起点

步骤

\(DFS1\)

功能
  • 标记每个点的深度
  • 标记每个点的父亲
  • 标记每个非叶子的子树大小(包括他自己)
  • 标记每个非叶子节点的重儿子的编号
代码实现
void dfs1(int x,int f,int deep)//x当前节点,f父亲,deep深度
{
	dep[x]=deep;//标记深度 
	fa[x]=f;//标记每个点的父亲 
	siz[x]=1;//标记每个非叶子节点的子树的大小 
	int maxson=-1;//记录重儿子的儿子数量
	for(int i=head[x];i;i=e[i].last)
	{
		int y=e[i].to;
		if(y==f) continue;//如果是父亲那么就继续去找下一个
		dfs1(y,x,deep+1);
		siz[x]+=siz[y];//加上子树的节点数量 
		if(siz[y]>maxson)
		{
			son[x]=y;
			maxson=siz[y];//如果该子节点更大,那么就标记他的每个非叶子节点的
			//重儿子的编号 
		}
	} 
}

\(DFS2\)

功能
  • 标记每一个点的新编号(在线段树里面的)
  • 给每个点的新编号赋上这个点的初始值
  • 处理好每个点所在的链的顶端
  • 处理每条链
代码实现(先处理重儿子,再处理轻儿子)
void dfs2(int x,int topf)//x为当前的节点,topf为当前链上最顶端的节点
{
	id[x]=++cnt;//标记每个点的新编号 
	wt[cnt]=w[x];//把每个点的初始值都赋到新的编号上来
	top[x]=topf;//标记这个点所在的链的顶端
	if(!son[x]) return;//如果没有重儿子(儿子),那么就返回
	dfs2(son[x],topf);//先处理重儿子,在处理轻儿子,递归处理
	for(int i=head[x];i;i=e[i].last)
	{
		int y=e[i].to;
		if(y==fa[x]||y==son[x])continue;
		//如果遍历到父亲结点或者是重儿子,那么就继续搜索
		dfs2(y,y);
		//每一个轻儿子都有一条从自己开始的链
	} 
} 

处理问题

  • 先标上新的编号


因为顺序是按照先重儿子,再轻儿子来处理的,所以每一条重链的新编号是连续的
因为是用的\(DFS\)所以每一个子树的新编号也是连续的

  • 首先,当我们要处理任意两点间的路径时:
    设我们所在练的顶端深度更深的那个点为\(x\)
    • \(ans\)加上\(x\)点到\(x\)所在链的顶端这一段区间的点权和
    • \(x\)跳到\(x\)所在的链顶端的那个点的上面的那个点
    • 不断地执行这两个操作,知道这两个点处在一条链上面,然后此时在加上这两个点之间的区间和

在这个时候我们注意到,我们所要处理的所有的区间都是连续的编号(新编号),那么我们可以用线段树处理连续编号区间和,每次查询时间复杂度为\(O(log^2n)\)

int qRange(int x,int y)
{
	int ans=0;
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]])//把x改在所在链更深的点
		swap(x,y);
		res=0;
		query(1,1,n,id[top[x]],id[x]);//ans加上x点到所在链顶端的这一区间的点权和
		ans+=res;
		ans%=mod;
		x=fa[top[x]]; 
	}
	if(dep[x]>dep[y])swap(x,y);
	res=0;
	query(1,1,n,id[x],id[y]);
	ans+=res;
	return ans%mod; 
 } 

处理一点及其子树的点权和

记录每一个非叶子节点的子树的大小,并且每一个子树的新编号都是连续的,于是就直接线段树区间查询即可时间复杂度为\(O(log n)\)

int qson(int x)
{
	res=0;
	query(1,1,n,id[x],id[x]+siz[x]-1);//子树的右端点为id[x]+siz[x]-1,可以手推一下
	return res;
}

区间修改

void updson(int x,int k)
{
	update(1,1,n,id[x],id[x]+siz[x]-1,k);
}
void updRange(int x,int y,int k)//区间修改
{
	k%=mod;
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]])//让x的深度更深 
		swap(x,y);
		update(1,1,n,id[top[x]],id[x],k);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	update(1,1,n,id[x],id[y],k);
}


完整代码(200行高能)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<stack>
#include<map>
#include<cmath>
#include<algorithm>
using namespace std;

#define mid ((l+r)>>1)
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
#define len (r-l+1)

const int N=2e5+10;
struct node{
	int last;
	int to;
}e[N];
int head[N]; 

int n,m,r,mod;
int e_cnt,w[N],wt[N];
int a[N<<2],laz[N<<2];
//线段树数组,懒惰标记
int son[N],id[N],fa[N],cnt,dep[N],siz[N],top[N];
//重儿子编号,新编号,父亲结点,dfs序,深度,子树的大小,当前链的顶端结点
int res=0;
void add(int from,int to)
{
	e[++e_cnt].last=head[from];
	e[e_cnt].to=to;
	head[from]=e_cnt;
}
//------------------------------------------------线段树
void pushdown(int rt,int lenn)
{
	laz[rt<<1]+=laz[rt];
	laz[rt<<1|1]+=laz[rt];
	a[rt<<1]+=laz[rt]*(lenn-(lenn>>1));
	a[rt<<1|1]+=laz[rt]*(lenn>>1);
	a[rt<<1]%=mod;
	a[rt<<1|1]%=mod;
	laz[rt]=0; 
}
void build(int rt,int l,int r)
{
	if(l==r)
	{
		a[rt]=wt[l];//赋值点权值
		if(a[rt]>mod) a[rt]%=mod;
		return; 
	}
	build(lson);
	build(rson);
	a[rt]=(a[rt<<1]+a[rt<<1|1])%mod;
}
void query(int rt,int l,int r,int L,int R)
{
	if(L<=l&&r<=R)
	{
		res+=a[rt];
		res%=mod;
		return;
	}
	else 
	{
		if(laz[rt]) pushdown(rt,len);
		if(L<=mid) query(lson,L,R);
		if(R>mid) query(rson,L,R);
	}
}
void update(int rt,int l,int r,int L,int R,int k)
//当前节点,当前区间的左,右,要修改的区间左,右,修改值 
{
	if(L<=l&&r<=R)
	{
		laz[rt]+=k;
		a[rt]+=k*len;
	}
	else
	{
		if(laz[rt]) pushdown(rt,len);
		if(L<=mid) update(lson,L,R,k);
		if(R>mid) update(rson,L,R,k);
		a[rt]=(a[rt<<1]+a[rt<<1|1])%mod;
	}
} 
//------------------------------------------------树链剖分
int qRange(int x,int y)
{
	int ans=0;
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]])//把x改在所在链更深的点
		swap(x,y);
		res=0;
		query(1,1,n,id[top[x]],id[x]);//ans加上x点到所在链顶端的这一区间的点权和
		ans+=res;
		ans%=mod;
		x=fa[top[x]]; 
	}
	if(dep[x]>dep[y])swap(x,y);
	res=0;
	query(1,1,n,id[x],id[y]);
	ans+=res;
	return ans%mod; 
 } 
void updRange(int x,int y,int k)//区间修改
{
	k%=mod;
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]])//让x的深度更深 
		swap(x,y);
		update(1,1,n,id[top[x]],id[x],k);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	update(1,1,n,id[x],id[y],k);
}
int qson(int x)
{
	res=0;
	query(1,1,n,id[x],id[x]+siz[x]-1);
	return res;
}
void updson(int x,int k)
{
	update(1,1,n,id[x],id[x]+siz[x]-1,k);
}
void dfs1(int x,int f,int deep)//x当前节点,f父亲,deep深度
{
	dep[x]=deep;//标记深度 
	fa[x]=f;//标记每个点的父亲 
	siz[x]=1;//标记每个非叶子节点的子树的大小 
	int maxson=-1;//记录重儿子的儿子数量
	for(int i=head[x];i;i=e[i].last)
	{
		int y=e[i].to;
		if(y==f) continue;//如果是父亲那么就继续去找下一个
		dfs1(y,x,deep+1);
		siz[x]+=siz[y];//加上子树的节点数量 
		if(siz[y]>maxson)
		{
			son[x]=y;
			maxson=siz[y];//如果该子节点更大,那么就标记他的每个非叶子节点的
			//重儿子的编号 
		}
	} 
}
void dfs2(int x,int topf)//x为当前的节点,topf为当前链上最顶端的节点
{
	id[x]=++cnt;//标记每个点的新编号 
	wt[cnt]=w[x];//把每个点的初始值都赋到新的编号上来
	top[x]=topf;//标记这个点所在的链的顶端
	if(!son[x]) return;//如果没有重儿子(儿子),那么就返回
	dfs2(son[x],topf);//先处理重儿子,在处理轻儿子,递归处理
	for(int i=head[x];i;i=e[i].last)
	{
		int y=e[i].to;
		if(y==fa[x]||y==son[x])continue;
		//如果遍历到父亲结点或者是重儿子,那么就继续搜索
		dfs2(y,y);
		//每一个轻儿子都有一条从自己开始的链
	} 
} 
int main()
{
	cin>>n>>m>>r>>mod;
	for(int i=1;i<=n;i++)
	cin>>w[i];//节点的初始权值 
	for(int i=1;i<n;i++)
	{
		int x,y;
		cin>>x>>y;
		add(x,y);
		add(y,x); 
	}
	dfs1(r,0,1);
	dfs2(r,r);
	build(1,1,n);
	while(m--)
	{
		int k,x,y,z;
		cin>>k;
		if(k==1)
		{
			cin>>x>>y>>z;
			updRange(x,y,z);
		}
		else if(k==2)
		{
			cin>>x>>y;
			cout<<qRange(x,y)<<endl;
		}
		else if(k==3)
		{
			cin>>x>>y;
			updson(x,y);
		}
		else 
		{
			cin>>x;
			cout<<qson(x)<<endl;
		}
	}
	return 0;
 }                         
posted @ 2020-11-23 21:48  琼瑾  阅读(177)  评论(4编辑  收藏  举报
Live2D