树链剖分学习笔记

题目来源:luogu P3348【模板】树链剖分

题目链接:https://www.luogu.org/problem/show?pid=3384

题目大意:已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:
1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z,
2 x y 表示求树从x到y结点最短路径上所有节点的值之和,
3 x z 表示将以x为根节点的子树内所有节点值都加上z,
4 x 表示求以x为根节点的子树内所有节点值之和.

树链剖分学习笔记正文:

在某一些问题中,通常要求对树上的某一条链进行修改或者查询操作。

针对这个问题的某些问题,一种行之有效的做法就是先预处理一遍,然后可以在之后O(lgn)的求出LCA,然后暴力的做。

例如对于单点加减询问链和的问题,可以模仿树状数组的在树上倍增的O(lgn)的修改和求和。

例如询问的是u到v的链的和,那么链和就是getsum(u)+getsum(v)-2*getsum(LCA(u,v))-value[LCA(u,v)].而且修改和询问的复杂度都是O(lgn)的,复杂度就比较好。

但如果问题是询问最值之类的不满足加减性的询问操作,但是信息本身可以合并,我们发现如果是在序列上的问题,可以用线段树来做。但是在树上就没有那么简单。所以我们要想出一种办法,将树的问题转化成序列的问题进行操作。通常的做法是树链剖分,也就是把树弄成序列,使得复杂度不是很高。注意,事实上树链剖分并不是一种具体的算法,而是一种算法思想。其中最常用的是轻重链剖分,能解决目前遇到的绝大多数问题,所以轻重链剖分有时候也直接说是树链剖分,即树链剖分大多数情况下指的是轻重链剖分。也有其他的树链剖分方法,在对应的问题中由意想不到的妙处,在此不做介绍。

算法过程基本如下:

树链剖分(注意,从这里开始的下文中,若无特殊指明,树链剖分指的都是轻重链剖分)的大致步骤是两次dfs。

第一次dfs,求出所有节点的深度depth,父亲节点father,以该节点为根的子树大小size,然后,在它的子树中选择一颗size最大的子树,作为他的重儿子son(如果有多个,随便选一个)。

第一遍dfs的过程很简单,不多做解释。

第二次dfs,是树链剖分的重点。我们首先要解释这样一些概念:

轻链和重链。每个点和它的重儿子之间的边称为重边。显然对于每个节点,都可以向下沿着重边走到底,向上一直走到它和父节点不是重边为止。那么从这个最上面的点,一直连到最下面的那个点,就叫做重链。轻边轻链的概念是类似的,不多做解释。一条重链的顶端,记作链顶top。

然后在第二次dfs中,首先将根节点的链顶更新(可以通过递归参数传递实现)。然后,先去dfs它的重儿子(如果有的话!),并且传递链顶参数的时候,传递的是正在dfs的节点的链顶。然后去遍历它的其它儿子(注意不能重复遍历重儿子),只不过轻儿子所在的链的链顶很显然就是它们本身。

除了top,还有一个要记录的就是dfs序dfn,或者dfs_clock之类的,就是该节点第一次被访问是第几个被访问的,下文用lef代替。有时候还需要记录当结束这个节点的dfs的时候,已经访问了几个节点(其实就是这个子树中最大的dfn)rig。

显然,我们有这样的性质:对于一条重链来讲,显然它上面的dfn/lef是连续的,因为我们每次都是从重儿子开始遍历。

同时,一颗子树rt中的dfn是连续的,因为dfs是递归的,而且还正好是lef~rig。

另外通常会将按照先遍历重儿子方式dfs得到的dfs序称为树剖序。

所以,对一条重链的修改等价于修改一段区间,对一个子树的修改还是等价于需改一个区间!

那我们再来看如何实现对一条链的修改。

假设我们要修改u到v一条链,方法是这样的:

如果它们两个已经在同一条链(即u.top==v.top),直接在线段树上修改即可(因为此时dfs序是连续的,对应的区间是[ min(u.lev,v.lef) , max(u.lev,v.lef) ])。

否则,看他们两个的链顶哪一个比较小,假设u.top.dep<v.top.dep,那么修改区间[u.top.dfn , u.dfn],然后让u=u.top.fa。

这样一直修改到两个在同一条链上为止。

复杂度的证明:并不太会,大概意思是说,在一条由root开始的路径,对于一条轻边(u,v),u.size>=2*v.size。所以轻边不会超过lgn条,所以重链不会超过lgn条。于是切出来的区间不会超过2lgn个(事实上,常数会非常小)。所以复杂度是O((lgn)^2)的。

丑陋的代码大概如下:

//luogu 3384
#include<iostream>
#include<cstdio>
#define MAXN 100010
using namespace std;
struct edges{
	int fa,ch,pre;
}edge[2*MAXN];int etop;
int n,m,rt,p,dfs_clock,opt,u,v,x,y;
int a[MAXN],times[MAXN];
struct tree{
	int size,father,top,head,depth,son,lef,rig;
	tree()
	{ size=father=top=head=lef=rig=son=0; }
}t[MAXN];
struct segment{
	int sumn,tag,lef,rig;
	segment *tc,*rc;
}*root;
int add_edge(int fa,int ch)
{
	etop++;
	edge[etop].fa=fa;
	edge[etop].ch=ch;
	edge[etop].pre=t[fa].head;
	t[fa].head=etop;return 0;
}
int fir_dfs(int rt,int depth,int fa)
{
	t[rt].depth=depth;
	t[rt].father=fa;
	t[rt].size=1;t[rt].son=0;
	for(int i=t[rt].head;i;i=edge[i].pre)
		if(edge[i].ch!=fa)
		{
			fir_dfs(edge[i].ch,depth+1,rt);
			t[rt].size+=t[edge[i].ch].size;
			if(t[edge[i].ch].size>t[t[rt].son].size)
				t[rt].son=edge[i].ch;
		}
	return 0;
}
int sec_dfs(int rt,int fa)
{
	t[rt].lef=++dfs_clock;
	times[t[rt].lef]=rt;
	if(t[rt].son!=0)
	{
		t[t[rt].son].top=t[rt].top;
		sec_dfs(t[rt].son,rt);
	}
	for(int i=t[rt].head;i;i=edge[i].pre)
		if(edge[i].ch!=fa&&edge[i].ch!=t[rt].son)
		{
			t[edge[i].ch].top=edge[i].ch;
			sec_dfs(edge[i].ch,rt);
		}
	t[rt].rig=dfs_clock;
}
int debug_dfs(int rt,int fa)
{
	printf("rt=%d lef=%d rig=%d depth=%d size=%d son=%d top=%d father=%d\n",rt,t[rt].lef,t[rt].rig,t[rt].depth,t[rt].size,t[rt].son,t[rt].top,t[rt].father);
	for(int i=t[rt].head;i;i=edge[i].pre)
		if(edge[i].ch!=fa)
			debug_dfs(edge[i].ch,rt);
	return 0;
}
int debug_segment(segment* &rt)
{
	int lef=rt->lef,rig=rt->rig,&tag=rt->tag,&sumn=rt->sumn;
	printf("lef=%d rig=%d tag=%d sumn=%d\n",rt->lef,rt->rig,tag,sumn);
	if(lef==rig) return 0;
	debug_segment(rt->tc);
	debug_segment(rt->rc);
	return 0;
}
int build_segment(segment* &rt,int lef,int rig)
{
	rt=new segment;
	rt->lef=lef;
	rt->rig=rig;
	rt->tc=rt->rc=NULL;
	rt->tag=0;
	if(lef==rig)
	{
		rt->sumn=a[times[lef]];
		return 0;
	}
	int mid=(lef+rig)/2;
	build_segment(rt->tc,lef,mid);
	build_segment(rt->rc,mid+1,rig);
	rt->sumn=rt->tc->sumn+rt->rc->sumn;
	return 0;
}
inline int update_tag(segment* &rt,int v)
{
	int lef=rt->lef,rig=rt->rig,&tag=rt->tag,&sumn=rt->sumn;
	tag=(tag+v)%p;sumn=((long long)v*(rig-lef+1)+sumn)%p;
	return 0;
}
int push_down(segment* &rt)
{
	update_tag(rt->tc,rt->tag);
	update_tag(rt->rc,rt->tag);
	rt->tag=0;return 0;
}
int push_up(segment* &rt)
{
	rt->sumn=(rt->rc->sumn+rt->tc->sumn)%p;
	return 0;
}
int update_segment(segment* &rt,int s,int t,int v)
{
	int lef=rt->lef,rig=rt->rig,&tag=rt->tag,&sumn=rt->sumn;
	if(s<=rt->lef&&rt->rig<=t)
	{
		tag=(tag+v)%p;
		sumn=((long long)v*(rig-lef+1)+sumn)%p;
		return 0;
	}
	if(tag) push_down(rt);
	int mid=(lef+rig)/2;
	if(s<=mid) update_segment(rt->tc,s,t,v);
	if(mid<t) update_segment(rt->rc,s,t,v);
	push_up(rt);return 0;
}
int query_segment(segment* &rt,int s,int t)
{
	int lef=rt->lef,rig=rt->rig,&tag=rt->tag,&sumn=rt->sumn;
	if(s<=rt->lef&&rt->rig<=t) return sumn;
	if(tag) push_down(rt);int mid=(lef+rig)/2,ans=0;
	if(s<=mid) ans=(query_segment(rt->tc,s,t)+ans)%p;
	if(mid<t) ans=(query_segment(rt->rc,s,t)+ans)%p;
	return ans;
}
int main()
{
	scanf("%d%d%d%d",&n,&m,&rt,&p);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	for(int i=1;i<n;i++)
	{
		scanf("%d%d",&u,&v);
		add_edge(u,v);add_edge(v,u);
	}
	fir_dfs(rt,1,0);
	dfs_clock=0;t[0].size=0;
	t[rt].top=rt;sec_dfs(rt,0);
//	debug_dfs(rt,0);
	build_segment(root,1,n);
//	debug_segment(root);
	while(m--)
	{
		scanf("%d",&opt);
		if(opt==1)
		{
			scanf("%d%d%d",&x,&y,&v);
			while(t[x].top!=t[y].top)
			{
				if(t[t[x].top].depth>t[t[y].top].depth)
				{
//					cout<<t[t[x].top].lef<<" "<<t[x].lef<<endl;
					update_segment(root,t[t[x].top].lef,t[x].lef,v);
					x=t[t[x].top].father;
				}
				else{
//					cout<<t[t[y].top].lef<<" "<<t[y].lef<<endl;
					update_segment(root,t[t[y].top].lef,t[y].lef,v);
					y=t[t[y].top].father;
				}
			}
			if(t[x].lef>t[y].lef) swap(x,y);
//			cout<<t[x].lef<<" "<<t[y].lef<<endl;
			update_segment(root,t[x].lef,t[y].lef,v);
//			debug_segment(root);
		}
		else if(opt==2)
		{
			scanf("%d%d",&x,&y);
			int ans=0;
			while(t[x].top!=t[y].top)
			{
				if(t[t[x].top].depth>t[t[y].top].depth)
				{
//					cout<<t[t[x].top].lef<<" "<<t[x].lef<<endl;
					ans=(ans+query_segment(root,t[t[x].top].lef,t[x].lef))%p;
					x=t[t[x].top].father;
				}
				else{
//					cout<<t[t[y].top].lef<<" "<<t[y].lef<<endl;
					ans=(ans+query_segment(root,t[t[y].top].lef,t[y].lef))%p;
					y=t[t[y].top].father;
				}
			}
			if(t[x].lef>t[y].lef) swap(x,y);
//			cout<<t[x].lef<<" "<<t[y].lef<<endl;
			ans=(ans+query_segment(root,t[x].lef,t[y].lef))%p;
			printf("%d\n",ans);
		}
		else if(opt==3)
		{
			scanf("%d%d",&x,&v);
			update_segment(root,t[x].lef,t[x].rig,v);
//			debug_segment(root);
		}
		else{
			scanf("%d",&x);
			printf("%d\n",query_segment(root,t[x].lef,t[x].rig));
		}
	}
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值