树链剖分

树链剖分

前言

  • 树链剖分听上去就挺高级,实际上,和线段树一样,理解其中的原理就简单得多。重点还是代码,码量极长,还是一句话,熟能生巧,多练。

转载:树链剖分详解 by ChinHhh

P3384 【模板】重链剖分/树链剖分

P3384

分析

基础板子题,注意细节。

code

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define lc (u<<1)
#define rc (u<<1|1)
const int N=1e5+5;

int n,m,r,mod,v[N];//基本数据
int tot,ver[N<<1],ne[N<<1],head[N<<1];//链式前向星(无边权)
int d[N],fa[N],cnt[N],son[N],id[N],w[N],top[N];//树链剖分
//点深度;父亲;子树大小;儿子;修改后的坐标;修改后的值;链顶
struct node 
{
	int l,r;
	ll tag,v;
}a[N<<2];//线段树

void add(int x,int y)
{
	ver[++tot]=y,ne[tot]=head[x],head[x]=tot;
}

//-----------------------------------------------线段树
void maketag(int u,ll x)
{
	a[u].tag+=x;
	a[u].v+=(a[u].r-a[u].l+1)*x;
	a[u].v%=mod;
}
void pushup(int u)
{
	a[u].v=(a[lc].v+a[rc].v)%mod;
}
void pushdown(int u)
{
	maketag(lc,a[u].tag);
	maketag(rc,a[u].tag);
	a[u].tag=0;
}
void build(int u,int l,int r)
{
	a[u].l=l,a[u].r=r;
	if(l==r) a[u].v=w[l];
	else 
	{
		int mid=(l+r)>>1;
		build(lc,l,mid);
		build(rc,mid+1,r);
		pushup(u);
	}
}
void modify(int u,int l,int r,ll x)
{
	int L=a[u].l,R=a[u].r;
	if(L>=l&&R<=r) maketag(u,x);
	else if(!(L>r||R<l))
	{
		pushdown(u);
		modify(lc,l,r,x);
		modify(rc,l,r,x);
		pushup(u);
	}
}
ll query(int u,int l,int r)
{
	int L=a[u].l,R=a[u].r;
	if(L>=l&&R<=r) return a[u].v;
	else if(!(L>r||R<l))
	{
		pushdown(u);
		return query(lc,l,r)+query(rc,l,r);
	}
	else return 0;
}
//-----------------------------------------------线段树

//-----------------------------------------------树链剖分
void dfs1(int u,int f)
{
	d[u]=d[f]+1;//更新深度
	fa[u]=f;//更新父亲
	cnt[u]=1;//更新子树大小
	int maxson=-1;//记录重儿子子树大小
	for(int i=head[u];i;i=ne[i])
	{
		int y=ver[i];
		if(y==f) continue;
		dfs1(y,u);
		cnt[u]+=cnt[y];
		if(cnt[y]>maxson) maxson=cnt[y],son[u]=y;
	}//更新重儿子和子树的大小
}
int idx;
void dfs2(int u,int topf)
{
	id[u]=++idx;//更新坐标
	w[idx]=v[u];//更新值
	top[u]=topf;//更新顶点
	if(son[u]) 
	{
		dfs2(son[u],topf);//先遍历重儿子
		for(int i=head[u];i;i=ne[i])
		{
			int y=ver[i];
			if(y!=fa[u]&&y!=son[u]) dfs2(y,y);//再遍历轻儿子
		}
	}
}
void modify_tree(int u,ll x)
{
	int nu=id[u];
	int l=nu,r=nu+cnt[u]-1;
	modify(1,l,r,x);
}
ll query_tree(int u)
{
	int nu=id[u];
	int l=nu,r=nu+cnt[u]-1;
	return query(1,l,r)%mod;
}
void modify_lian(int x,int y,int z)
{
	while(top[x]!=top[y])//先从下往上爬
	{
		if(d[top[x]]<d[top[y]]) swap(x,y);//从更深的点往上
		modify(1,id[top[x]],id[x],z);
		x=fa[top[x]];
	}
	if(d[x]>d[y]) swap(x,y);//保证区间左端点小于右端点
	modify(1,id[x],id[y],z);//再从上往下
}
ll query_lian(int x,int y)
{//同上
	ll ans=0;
	while(top[x]!=top[y])
	{
		if(d[top[x]]<d[top[y]]) swap(x,y);
		ans+=query(1,id[top[x]],id[x]);
		ans%=mod;
		x=fa[top[x]];
	}
	if(d[x]>d[y]) swap(x,y);
	ans+=query(1,id[x],id[y]);
	return ans%mod;
}
//-----------------------------------------------树链剖分

int main ()
{
	// freopen("1.in","r",stdin);
	// freopen("1.out","w",stdout);
	ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
	int op,x,y,z;
	cin>>n>>m>>r>>mod;
	for(int i=1;i<=n;i++) cin>>v[i],v[i]%=mod;
	for(int i=1;i<n;i++)
	{
		cin>>x>>y;
		add(x,y);
		add(y,x);
	}
	dfs1(r,0);
	dfs2(r,r);
	build(1,1,n);
	for(int i=1;i<=m;i++)
	{
		cin>>op;
		if(op==1) cin>>x>>y>>z,modify_lian(x,y,z%mod);
		else if(op==2) cin>>x>>y,cout<<query_lian(x,y)<<"\n";
		else if(op==3) cin>>x>>z,modify_tree(x,z%mod);
		else cin>>x,cout<<query_tree(x)<<"\n";
	}
	return 0;
}

tip

  • 树要存双向边
  • 答案要取模
  • 链式前向星的数组要开两倍
posted @ 2024-01-03 21:35  zhouruoheng  阅读(9)  评论(0编辑  收藏  举报