【学习笔记】树链剖分&板子

学完了树剖,写下总结&板子:

参考:OIWIKI这个博客

树链剖分:这里说下最常用的轻重链剖分,首先定义重子节点:子节点中子树最大的子节点,其余的就是轻子节点;重边:从这个结点到重子节点的边,到其余的子节点为轻边;重链:若干条首尾衔接的重边构成重链。

为什么要用重链剖分呢?因为它有几个很好的性质来方便地维护树上路径的信息:

<1>重链剖分可以将树上的任意一条路径划分为不超过O(logn)条连续的链,每条链上的结点深度各不相同。

<2>重链剖分还能保证划分出的每条链上的结点dfs序连续(这就很重要了,比如你想让x到y路径上结点值区间修改,就能用线段树来维护了)

于是我们就可以有像模板题中的几种操作:

<1>将树从x到y结点最短路径上的所有结点的值都加上z

<2>求树从x到y结点最短路径上所有结点的值的和

<3>将以x为根节点的子树内所有结点值都加上z

<4>求以x为根节点的子树内所有结点值之和

(具体操作见后文)

然后我们来看下如何进行树剖,主要有这样几个步骤:

<1>第一个dfs记录每个结点的父节点fa,深度d,子树大小tot,重子节点son

<2>第二个dfs记录所在链的链顶top,重边优先遍历的dfs序idx,dfs序对应的结点编号rk(有时不用)

然后就有这样几个数组和变量:

int n;//节点数目
int mod;//题目中的mod
int d[maxn];//d[i]表示第i个结点的深度
int fa[maxn];//fa[i]表示第i个结点的parent
int son[maxn];//son[i]表示第i个结点的重子节点
int tot[maxn];//tot[i]表示以第i个结点为根节点的子树结点个数
int top[maxn];//top[i]表示第i个结点所在重链的顶部节点
int idx[maxn];//idx[i]表示第i个结点对应的dfs序
int a[maxn];//a[i]表示dfs序为i的结点的初值
int w[maxn];//w[i]表示第i个结点的初值
int ct=0;//辅助记录idx和a,没啥用
int m,r;//m次询问,r为树的根

<3>用求到的a数组建立线段树

<4>各种操作

4.1 将树从x到y结点最短路径上的所有结点的值都加上z

即xy路径上维护,我们每次选择深度较大的链往上跳,直到两点在同一条链上。(代码应该比较直观,能直接看明白,或者手动模拟一下)

void tree_add(int x,int y,int val)
{
	while(top[x]!=top[y]){
		if(d[top[x]]<d[top[y]]) swap(x,y);
		add(1,idx[top[x]],idx[x],val);
		x=fa[top[x]];
	}
	if(d[x]>d[y]) swap(x,y);
	add(1,idx[x],idx[y],val);
}

4.2 求树从x到y结点最短路径上所有结点的值的和

这个和4.1思路是一样的,就是变成查询了:

int tree_sum(int x,int y)
{
	int res=0;
	while(top[x]!=top[y]){
		if(d[top[x]]<d[top[y]]) swap(x,y);
		res=(res+search(1,idx[top[x]],idx[x]))%mod;
		x=fa[top[x]];
	}
	if(d[x]>d[y]) swap(x,y);
	res=(res+search(1,idx[x],idx[y]))%mod;
	return res;
}

4.3 将以x为根节点的子树内所有结点值都加上z

由于子树中结点的dfs序是连续的,所以直接就变成区间维护了,左边界是idx[x],右边界是idx[x]+tot[x]-1,即子树中最后一个dfs序的结点。所以直接在线段树上操作就行:

add(1,idx[x],idx[x]+tot[x]-1,z);

4.4 求以x为根节点的子树内所有结点值之和

这个跟4.3是一样的,直接在线段树上操作:

search(1,idx[x],idx[x]+tot[x]-1);

4.5 补充个OIWIKI的LCA:(跟代码里的变量不一样)

int lca(int u, int v) {
  while (top[u] != top[v]) {
    if (dep[top[u]] > dep[top[v]])
      u = fa[top[u]];
    else
      v = fa[top[v]];
  }
  return dep[u] > dep[v] ? v : u;
}

板子:(模板题:洛谷P3384

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn=2e5+10;
struct edge{
	int to,next;
}e[maxn<<1];
int head[maxn],cnt=0;
int n;//节点数目
int mod;//题目中的mod
int d[maxn];//d[i]表示第i个结点的深度
int fa[maxn];//fa[i]表示第i个结点的parent
int son[maxn];//son[i]表示第i个结点的重子节点
int tot[maxn];//tot[i]表示以第i个结点为根节点的子树结点个数
int top[maxn];//top[i]表示第i个结点所在重链的顶部节点
int idx[maxn];//idx[i]表示第i个结点对应的dfs序
int a[maxn];//a[i]表示dfs序为i的结点的初值
int w[maxn];//w[i]表示第i个结点的初值
int ct=0;//辅助记录idx和a,没啥用
int m,r;//m次询问,r为树的根
//多组输入的时候记得初始化:head,cnt,ct,tree,son
void add_edge(int u,int v)
{
	cnt++;
	e[cnt].to=v;
	e[cnt].next=head[u];
	head[u]=cnt;
}
struct node{
	int left,right;
	int lazy;
	int sum;
	int siz;
}tree[maxn<<2];
void pushup(int root)
{
	tree[root].sum=(tree[root<<1].sum+tree[root<<1|1].sum+mod)%mod;
}
void pushdown(int root)
{
	if(!tree[root].lazy) return;
	tree[root<<1].sum=(tree[root<<1].sum+tree[root].lazy*tree[root<<1].siz)%mod;
	tree[root<<1|1].sum=(tree[root<<1|1].sum+tree[root].lazy*tree[root<<1|1].siz)%mod;
	tree[root<<1].lazy=(tree[root<<1].lazy+tree[root].lazy)%mod;
	tree[root<<1|1].lazy=(tree[root<<1|1].lazy+tree[root].lazy)%mod;
	tree[root].lazy=0;
}
void build(int root,int left,int right)
{
	tree[root].left=left,tree[root].right=right;
	tree[root].siz=right-left+1;
	if(left==right){
		tree[root].sum=a[left];
		return;
	}
	int mid=(left+right)>>1;
	build(root<<1,left,mid);
	build(root<<1|1,mid+1,right);
	pushup(root);
}
void add(int root,int left,int right,int val)
{
	if(left<=tree[root].left&&tree[root].right<=right){
		tree[root].sum=(tree[root].sum+tree[root].siz*val)%mod;
		tree[root].lazy=(tree[root].lazy+val)%mod;
		return;
	}
	pushdown(root);
	if(tree[root<<1].right>=left) add(root<<1,left,right,val);
	if(tree[root<<1|1].left<=right) add(root<<1|1,left,right,val);
	pushup(root);
}
int search(int root,int left,int right)
{
	if(left<=tree[root].left&&tree[root].right<=right) return tree[root].sum;
	pushdown(root);
	int res=0;
	if(tree[root<<1].right>=left) res=(res+search(root<<1,left,right))%mod;
	if(tree[root<<1|1].left<=right) res=(res+search(root<<1|1,left,right))%mod;
	return res;
}
int dfs1(int now,int pre,int dep)
{
	d[now]=dep;
	fa[now]=pre;
	tot[now]=1;
	int mxson=-1;
	for(int i=head[now];i;i=e[i].next){
		int nxt=e[i].to;
		if(nxt==pre) continue;
		tot[now]+=dfs1(nxt,now,dep+1);
		if(tot[nxt]>mxson) mxson=tot[nxt],son[now]=nxt;
	}
	return tot[now];
}
void dfs2(int now,int topf)
{
	idx[now]=++ct;
	a[ct]=w[now];
	top[now]=topf;
	if(!son[now]) return;
	dfs2(son[now],topf);
	for(int i=head[now];i;i=e[i].next){
		int nxt=e[i].to;
		if(nxt==fa[now]||nxt==son[now]) continue;
  		dfs2(nxt,nxt);
	}
}
void tree_add(int x,int y,int val)
{
	while(top[x]!=top[y]){
		if(d[top[x]]<d[top[y]]) swap(x,y);
		add(1,idx[top[x]],idx[x],val);
		x=fa[top[x]];
	}
	if(d[x]>d[y]) swap(x,y);
	add(1,idx[x],idx[y],val);
}
int tree_sum(int x,int y)
{
	int res=0;
	while(top[x]!=top[y]){
		if(d[top[x]]<d[top[y]]) swap(x,y);
		res=(res+search(1,idx[top[x]],idx[x]))%mod;
		x=fa[top[x]];
	}
	if(d[x]>d[y]) swap(x,y);
	res=(res+search(1,idx[x],idx[y]))%mod;
	return res;
}
int main()
{
	scanf("%d%d%d%d",&n,&m,&r,&mod);
	for(int i=1;i<=n;i++) scanf("%d",&w[i]);
	for(int i=1;i<n;i++){
		int u,v;scanf("%d%d",&u,&v);
		add_edge(u,v);
		add_edge(v,u);
	}
	dfs1(r,0,1);
	dfs2(r,r);
	build(1,1,n);
	while(m--){
		int opt;scanf("%d",&opt);
		int x,y,z;
		int ans;
		if(opt==1){
			scanf("%d%d%d",&x,&y,&z);
			z%=mod;
			tree_add(x,y,z);
		}
		else if(opt==2){
			scanf("%d%d",&x,&y);
			ans=tree_sum(x,y);
			printf("%d\n",ans);
		}
		else if(opt==3){
			scanf("%d%d",&x,&z);
			z%=mod;
			add(1,idx[x],idx[x]+tot[x]-1,z);
		}
		else if(opt==4){
			scanf("%d",&x);
			ans=search(1,idx[x],idx[x]+tot[x]-1);
			printf("%d\n",ans);
		}
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值