树链剖分模板

树链剖分:树上统计的一种优秀算法,基本思想是用将树切割成几段长链再配合数据结构转为区间问题。

我使用了线段树,需要注意的是树链剖分的额外空间极大,需要尽可能压缩

线段树的部分并无改动。

关键在于剖分的过程。

分两次dfs完成

第一遍求出每颗子树大小;每个节点的父亲节点、深度、重儿子(即子树大小最大的儿子)

第二遍按照重儿子优先的原则将节点重新编号,分成长链,这样的做法可以使每条链上的序号连续,而深搜使各子树中的节点集合也为连续区间。建立线段树

 操作:

如果操作某节点x子树上的关键值,较简单,操作序号id[x]~id[x]+size[x]-1(易证)

如果操作两点间路径上的关键值,稍复杂,每次将两点所在链分别处理直到两点位于同一链即可

下面是程序

#include<iostream>
#include<cstdio>
#include<cstring>
#define MAXN 100001
using namespace std;
int n,m,r,p;
int a[MAXN];
int h,head[MAXN];
int fa[MAXN],size[MAXN],dep[MAXN],hson[MAXN];
int id[MAXN],top[MAXN],tot;
struct Edge
{
	int v,next;
}edge[MAXN*2];
struct node
{
	int lazy,sum;
}tr[MAXN*4];
void change(int o,int nl,int nr,int l,int r,int a)
{
	if(nl==l&&nr==r)
	{
	    tr[o].lazy+=a;
		tr[o].sum+=a*(nr-nl+1);
		return;
	}
	int mid=(nl+nr)>>1;
	change(o<<1,nl,mid,nl,mid,tr[o].lazy);change(o<<1|1,mid+1,nr,mid+1,nr,tr[o].lazy);
	tr[o].lazy=0;
	if(r<=mid) change(o<<1,nl,mid,l,r,a);
	else if(l>mid) change(o<<1|1,mid+1,nr,l,r,a);
	else change(o<<1,nl,mid,l,mid,a),change(o<<1|1,mid+1,nr,mid+1,r,a);
	tr[o].sum=(tr[o<<1].sum+tr[o<<1|1].sum)%p;
}
int ask(int o,int nl,int nr,int l,int r)
{
	if(l==nl&&r==nr)
	{
		return tr[o].sum;
	}
	int mid=(nl+nr)>>1;
	change(o<<1,nl,mid,nl,mid,tr[o].lazy);change(o<<1|1,mid+1,nr,mid+1,nr,tr[o].lazy);
	tr[o].lazy=0;
	if(r<=mid) return ask(o<<1,nl,mid,l,r);
	else if(l>mid) return ask(o<<1|1,mid+1,nr,l,r);
	else return ask(o<<1,nl,mid,l,mid)+ask(o<<1|1,mid+1,nr,mid+1,r);
}
void add(int u,int v)
{
	h++;
	edge[h].v=v;
	edge[h].next=head[u];
	head[u]=h;
}
void dfs1(int last,int u)
{
	fa[u]=last;
	dep[u]=dep[last]+1;
	size[u]=1;
	int maxx=-1;
	for(int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].v;
		if(v==last) continue;
		dfs1(u,v);
		size[u]+=size[v];
		if(size[v]>maxx) maxx=size[v],hson[u]=v;
	}
}
void dfs2(int u,int topf)
{
	id[u]=++tot;
    top[u]=topf;
    change(1,1,n,tot,tot,a[u]);
    if(!hson[u]) return;
    dfs2(hson[u],topf);
    for(int i=head[u];i;i=edge[i].next)
    {
    	int v=edge[i].v;
    	if(v==fa[u]||v==hson[u]) continue;
    	dfs2(v,v);
	}
}
int ask_tree1(int x,int y)
{
	int res=0;
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		res=(res+ask(1,1,n,id[top[x]],id[x]))%p;
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	res=(res+ask(1,1,n,id[x],id[y]))%p;
	return res;
}
int ask_tree2(int x)
{
	return ask(1,1,n,id[x],id[x]+size[x]-1);
}
void change_tree1(int x,int y,int a)
{
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		change(1,1,n,id[top[x]],id[x],a);
		x=fa[top[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	change(1,1,n,id[x],id[y],a);
} 
void change_tree2(int x,int a)
{
	change(1,1,n,id[x],id[x]+size[x]-1,a);
}
int main()
{
	cin>>n>>m>>r>>p;
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<n;i++)
	{
		int a,b;
		scanf("%d%d",&a,&b);
		add(a,b);add(b,a);
	}
	dfs1(0,r);dfs2(r,r);
	for(int i=1;i<=m;i++)
	{
		int op,x,y,z;
		scanf("%d",&op);
		if(op==1)
		{
			scanf("%d%d%d",&x,&y,&z);
			change_tree1(x,y,z);
		}
		if(op==2)
		{
			scanf("%d%d",&x,&y);
			printf("%d\n",ask_tree1(x,y));
		}
		if(op==3)
		{
			scanf("%d%d",&x,&y);
			change_tree2(x,y);
		}
		if(op==4)
		{
			scanf("%d",&x);
			printf("%d\n",ask_tree2(x));
		}
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值