【模板】树链剖分

原文

相当于把树上的链抽出来变成区间进行操作。

重链剖分(本文重点):

本文依照P3384 【模板】轻重链剖分/树链剖分的实现进行讲解。

就是剖分出重链覆盖全树。

重链:除开头的端点为轻儿子外,其他节点全为重儿子的链。

重儿子:一个节点的儿子中子树大小最大的儿子。

要进行树链剖分,先要进行预处理。需要处理出以下信息:

  • t o p [ u ] top[u] top[u] u节点所在重链的开头端点的编号。
  • d e p [ u ] dep[u] dep[u] u节点的深度。
  • f a [ u ] fa[u] fa[u] u节点的父亲节点的编号。
  • d f n [ u ] dfn[u] dfn[u] 时间戳,u节点被遍历到的顺序。
  • r a n k [ x ] rank[x] rank[x] 时间戳为x的节点的编号。
  • s i z e [ u ] size[u] size[u] 以u为根的子树大小。
  • s o n [ u ] son[u] son[u] u节点的重儿子的编号。

总共七个(七国之乱

预处理我们分为两个 dfs 来做。因为在重链剖分中优先遍历重儿子(为了使一条重链上的 d f n dfn dfn 连续)

第一次 dfs 处理除 t o p top top d f n dfn dfn r a n k rank rank 外的基本信息把重链基本的剖分出来。

void dfs1(int u,int f)
{
	fa[u]=f;
	size[u]=1;
	int tmp=-1;
	for(int i=head[u];~i;i=nxt[i])
	{
		if(to[i]==f) continue;
		dep[to[i]]=dep[u]+1;
		dfs1(to[i],u);
		size[u]+=size[to[i]];
		if(size[to[i]]>tmp)
		{
			tmp=size[to[i]];
			son[u]=to[i];
		}
	}
}

第二次就主要为了把节点信息标完整。

void dfs2(int u,int tp)
{
	dfn[u]=++idx;
	rk[idx]=u;
	top[u]=tp;
	if(!son[u]) return;
	dfs2(son[u],tp);
	for(int i=head[u];~i;i=nxt[i])
	{
		if(to[i]==son[u]||to[i]==fa[u]) continue;
		dfs2(to[i],to[i]); 
	}
} 

然后考虑用线段树维护被用 d f n dfn dfn 从原树中提取出来的区间。

线段树写法没有特殊,按照普通的线段树写即可。

唯一需要注意的是线段树的建树(见注释处)

void build(int i,int l,int r)
{
	t[i].l=l;t[i].r=r;t[i].mid=(l+r)>>1;
	t[i].d=t[i].add=0;
	if(l==r)
	{
		t[i].d=a[rk[l]]; //因为下标l,r是dfn
		return;
	}
	build(i<<1,l,t[i].mid);
	build(i<<1|1,t[i].mid+1,r);
	t[i].d=(t[i<<1].d+t[i<<1|1].d)%p;
}

其他部分和普通线段树是一样的。

然后逐一分析题目中的4个操作。

  • 1 x y z,表示将树从 x 到 y 结点最短路径上所有节点的值都加上 z。

  • 2 x y,表示求树从 x 到 y 结点最短路径上所有节点的值之和。

  • 3 x z,表示将以 x 为根节点的子树内所有节点值都加上 z。

  • 4 x,表示求以 x 为根节点的子树内所有节点值之和。

操作3,4非常好实现:

void mtree(int x,LL z)
{
	z%=p;
	t.modify_add(1,dfn[x],dfn[x]+size[x]-1,z);
}
LL qtree(int x)
{
	return t.query(1,dfn[x],dfn[x]+size[x]-1)%p;
}

因为区间下标是 d f n dfn dfn 一个节点与其子树构成的区间是连续的。

再看操作1,2。我们分情况讨论,对于在同一重链上的两个点,我们直接对它们的 d f n dfn dfn 区间进行操作即可。

对于不在同一重链上的两点,我们选择它们中所在重链的开头端点深度更深的(这样的目的是为了防止影响到无关的节点),对于它到其链顶进行区间操作,然后跳到它链顶的父亲,超级加辈,重复执行一直到两点在同一条重链上。

实现如下:

void mchain(int x,int y,LL z)
{
	z%=p;
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]]) Swap(x,y);
		t.modify_add(1,dfn[top[x]],dfn[x],z);
		x=fa[top[x]];
	}
	if(dfn[x]>dfn[y]) Swap(x,y); 
	t.modify_add(1,dfn[x],dfn[y],z);
}
LL qchain(int x,int y)
{
	LL res=0;
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]]) Swap(x,y);
		res=(res+t.query(1,dfn[top[x]],dfn[x]))%p;
		x=fa[top[x]];
	}
	if(dfn[x]>dfn[y]) Swap(x,y);
	res=(res+t.query(1,dfn[x],dfn[y]))%p;
	return res;
}

然后具体套用函数就好了。

对于维护边权的情况,我们只需要在 dfs1 中维护 a 数组,把该点的点权赋值为与父亲相连的边的边权就行了,本质上是边权转成点权。但是记得为根节点赋值。还有一个需要注意的就是在查询和修改链的时候,在同一条重链上的情况,我们在选择的区间应该是 [ d f n [ x ] + 1 , d f n [ y ] ] [dfn[x]+1,dfn[y]] [dfn[x]+1,dfn[y]],这样做可以防止 x 节点到其父节点的边权被算入答案。其他部分与普通树剖一致。

然后关于树链剖分求LCA

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值