树链剖分

引入

如果在一棵树中,有操作:
1:把结点X的权值变成Y。
∵在树中,所以先用dfs序把树变成链。

2询问从点u到点v的路径上的节点的最大权值。
树上ST即可

3把某个节点 x 为根的子树中所有点的点权都增加 a 。
这时就需要用线段树维护子树。

4询问某个节点 x 到根的路径中所有点的点权和。
那这个怎么解决呢?就需要用树链剖分了。
并且树剖可以完美的吧这几个操作都一起解决,剩了很多事(但是代码还是很长)。
树剖,就是把一棵树按轻重链来分成若干条链。所谓重,就是指一个点的子节点很多。
化分的方法如图:
在这里插入图片描述
其中红线连接的是重儿子,并且每个点只有一个重儿子和连接它的一条重边。
而这些重边有会形成若干条链,每条重链都由一条轻边连接,这样就实现了轻重链划分。
而从任何一个点都可以先到该点属于的链的顶部,再跨过一条轻边,如此反复,一定能达到根节点。
这样,我们可以把每条链看作一段区间,用线段树lazy维护。
所以,实现步骤:
1:pre记录每个点的深度,儿子的个数和父亲。在求儿子时顺便求出重儿子。
2:求dfs序,并先走重儿子,这样才能在线段树中形成连续的区间。
同时记录每个点在线段树中的编号,和每个编号对应的点。(既可以通过点求编号,也可以通过编号知道是哪个点)。
另外,同时也要记录这个点所在的链的链顶,方便后面在链中跳跃。
3:线段树的建树等等。
4:在链中跳跃。(详见代码)
好了,说了这么多,来一道例题。

例题

P3178 [HAOI2015]树上操作

分析

树剖模板题。

代码

#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define full(a,b) memset(a,b,sizeof a)
#define N 100005
int n,m,arr[N];
int head[N],ecnt,nxt[2*N],to[2*N];
int dad[N],dep[N],size[N],son[N];
int top[N],id[N],rev[N];
long long ans,s[4*N],lazy[4*N];
void init()
{
	full(head,-1);
}
int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9') f=ch=='-'?-1:1,ch=getchar();
	while(ch>='0'&&ch<='9') x=10*x+ch-'0',ch=getchar();
	return x*f;
}
void add(int u,int v)
{
	to[++ecnt]=v;
	nxt[ecnt]=head[u];
	head[u]=ecnt;
}
void pre(int u,int fa)//求深度,父亲和子节点个数 
{
	dep[u]=dep[fa]+1;
	dad[u]=fa;
	size[u]=1;
	for(int i=head[u]; i!=-1; i=nxt[i])
	{
		int v=to[i];
		if(v==fa) continue;
		pre(v,u);
		size[u]+=size[v];
		if(size[v]>size[son[u]]) son[u]=v;
	}
}
void dfsx(int u)//求dfs序和链顶 
{
	int v=son[u];
	if(v)
	{
		id[v]=++id[0];
		top[v]=top[u];
		rev[id[0]]=v;
		dfsx(v);
	}
	for(int i=head[u]; i!=-1; i=nxt[i])
	{
		v=to[i];
		if(top[v]) continue;
		id[v]=++id[0];
		top[v]=v;
		rev[id[0]]=v;
		dfsx(v);
	}
}
//线段树↓↓↓ 
void built(int k,int l,int r)
{
	if(l==r)
	{
		s[k]=arr[rev[l]];
		return;
	}
	int mid=(l+r)>>1;
	built(k*2,l,mid);
	built(k*2+1,mid+1,r);
	s[k]=s[2*k]+s[2*k+1];
}
void pushdown(int k,int l,int r)
{
	if(!lazy[k]) return;
	int mid=(l+r)>>1;
	s[2*k]+=lazy[k]*(mid-l+1);
	lazy[2*k]+=lazy[k];
	s[2*k+1]+=lazy[k]*(r-mid);
	lazy[2*k+1]+=lazy[k];
	lazy[k]=0;
}
void update(int k,int l,int r,int x,int y,long long v)
{
	if(x>r||y<l) return;
	if(x<=l&&r<=y)
	{
		s[k]+=v*(r-l+1);
		if(x!=y) lazy[k]+=v;
		return;
	}
	int mid=(l+r)>>1;
	pushdown(k,l,r);
	update(2*k,l,mid,x,y,v);
	update(2*k+1,mid+1,r,x,y,v);
	s[k]=s[2*k]+s[2*k+1];
}
void ask(int k,int l,int r,int x,int y)
{
	if(x>r||y<l) return;
	if(x<=l&&r<=y)
	{
		ans+=s[k];
		return;
	}
	int mid=(l+r)>>1;
	pushdown(k,l,r);
	ask(2*k,l,mid,x,y);
	ask(2*k+1,mid+1,r,x,y);
}
//以上是线段树模板 
void query(int u,int v)
{
	int fu=top[u],fv=top[v];//记录两个点的链顶 
	while(fu!=fv)//没有到一起 
	{
		if(dep[fu]<dep[fv]) swap(u,v),swap(fu,fv);//选择深度大的往上跳 
		ask(1,1,n,id[fu],id[u]);//更新线段树 
		u=dad[fu];fu=top[u];//跳跃 
	}
	if(id[u]>id[v]) swap(u,v);
	ask(1,1,n,id[u],id[v]);//处理最后的 
}
int main()
{
//	freopen("「HAOI2015」树上操作.in","r",stdin);
//	freopen("「HAOI2015」树上操作.out","w",stdout);
	init();
	n=read(),m=read();
	for(int i=1; i<=n; i++)
		arr[i]=read();
	for(int i=1; i<n; i++)
	{
		int x=read(),y=read();
		add(x,y);add(y,x);
	}
	pre(1,0);
	id[1]=++id[0];//根节点无法在dfsx中赋值,单独赋值 
	top[1]=1;
	rev[1]=1;
	dfsx(1);
	built(1,1,n);
	for(int i=1; i<=m; i++)
	{
		int flag=read();
		if(flag==3)
		{
			int x=read();
			ans=0;
			query(1,x);//从X到根节点求和 
			printf("%lld\n",ans);
		}
		else
		{
			int x=read(),a=read();
			if(flag==1) update(1,1,n,id[x],id[x],(long long)(a));//单点修改就是[x~x] 
			else update(1,1,n,id[x],id[x]+size[x]-1,(long long)(a));//id[x]+size[x]-1为X的子树 
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值