【算法竞赛学习笔记】dfs序与树链剖分-超有用的图论详解

本文介绍了树链剖分及其在图论问题中的应用,包括支持子树和路径上的节点求和、修改、查询等操作。通过dfs序将树拉直,利用线段树实现O(nlogn)预处理和O(1)查询k次祖先,以及O(n)统计子树信息。文章还讨论了长链剖分、重链剖分的性质和应用,并提供了两个实例题目代码,展示树链剖分在解决路径修改和查询问题上的能力。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


title : dfs序与树链剖分
date : 2021-8-12
tags : 图论,ACM
author : Linno


在这里插入图片描述

支持操作

在子树上或者是两点路径上的所有节点的求和、修改、查询等操作。

具体操作大致为:

(1)dfs进行树链剖分处理节点信息;

(2)根据dfs序把树拉直然后套数据结构。

dfs序

把树搞成了“连续的”。

一个节点的子树上的节点的时间戳,一定大于这个节点的时间戳且连续。

某些链上的时间戳也是连续的。

因为dfs序的性质——重链上的时间戳是连续的。

树链剖分

重链剖分——常用

O(logn)O(logn)O(logn)

将每个点子树大小最大的儿子标记为重儿子。

长链剖分——不常用

O(n)O(\sqrt n)O(n)

长链剖分十分类似于轻重链剖分,但是我们稍加修改,将每次选择子树大小最大的儿子作为重儿子变成了选择子树深度最大的那个儿子作为重儿子。然后将所有点和它的重儿子之间的边认为是重边,如果我们把他们在树中全部加粗,那么原树就被分割成了若干条链。

性质

(1)所有链的长度和是O(n)级别的。

(2)任意一点x的k级祖先y所在的长链长度大于等于k

(3)任意一点到根节点的重链数量不超过\sqrt n

应用

(1)O(nlogn)预处理,O(1)算k次祖先。

对于每一条长链的顶端x,记长链长度为L,预处理x沿长链从上往下的每一个点,以及x的L个祖先。要求点u的k次祖先,取r=2pr=2^pr=2p(p+1是k二进制的最高位),r>k/2,u的r次祖先由预处理得到,记为y。由性质2,y的k-r次祖先可直接求得。

(2)O(n)统计每个子树中以深度为下标的可合并信息。
用指针维护动规数组,在重儿子传递给父亲时将指针左移/右移一位。对于轻儿子,暴力统计信息。

实链剖分(搞LCT)

重儿子:一个节点的所有儿子中,大小最大的儿子(只有1个,如果相等则随便选)

轻儿子:一个节点除了重儿子之外的儿子都是轻儿子

重链:从一个轻儿子开始(根节点也是轻儿子),一路向重儿子走,连出的链叫做重链。除根节点外的任何一个节点的父亲节点都一定在一条重链上。

轻链:除了重链全是轻链

剖分好再dfs一遍标出dfs序即可实现上述四种操作。

树链剖分与线段树

树链剖分和线段树可以结合使用,来解决树上路径修改、查询的问题。

LuoguP4315 月下“毛景树”
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+7;
const int mod=1e9+7;

struct E{
	int v,w,n;
}e[N<<2];

int head[N],bk[N<<1],cnt=0;
inline void addedge(int u,int v,int w){
	e[++cnt]=(E){v,w,head[u]};head[u]=cnt;
	e[++cnt]=(E){u,w,head[v]};head[v]=cnt;
}

int sz[N],fa[N],son[N],val[N],dep[N],dfn[N],idx,idfn[N],bel[N];
void dfs1(int x){
	sz[x]=1;
	for(int i=head[x];i;i=e[i].n){
		int to=e[i].v;
		if(to==fa[x]) continue;
		fa[to]=x;
		val[to]=e[i].w;
		dep[to]=dep[x]+1;
		dfs1(to);
		sz[x]+=sz[to];
		if(sz[son[x]]<sz[to]) son[x]=to;
	}
}

void dfs2(int x,int tp){
	dfn[x]=++idx;
	idfn[idx]=x;
	bel[x]=tp;
	if(son[x]) dfs2(son[x],tp);
	for(int i=head[x];i;i=e[i].n){
		int to=e[i].v;
		if(to==son[x]||to==fa[x]) continue;
		dfs2(to,to);
	}
}

int tr[N<<2],lazy[N<<2],tg[N<<2];
#define MID int mid=l+r>>1
#define ls p<<1
#define rs p<<1|1
void build(int p,int l,int r){
	tg[p]=-1;
	if(l==r){
		tr[p]=val[idfn[l]];
		return;
	}
	MID;
	build(ls,l,mid);
	build(rs,mid+1,r);
	tr[p]=max(tr[ls],tr[rs]);
}

void pushdown(int p,int l,int r){
	if(tg[p]>=0){
		lazy[ls]=lazy[rs]=0;
		tg[ls]=tg[p];tg[rs]=tg[p];
		tr[ls]=tg[p];tr[rs]=tg[p];
		tg[p]=-1;
	}
	if(lazy[p]){
		lazy[ls]+=lazy[p];
		lazy[rs]+=lazy[p];
		tr[ls]+=lazy[p];
		tr[rs]+=lazy[p];
		lazy[p]=0;
	}
}
 
void cover(int p,int l,int r,int ql,int qr,int w){
	if(ql<=l&&r<=qr){
		tr[p]=w;
		tg[p]=w;
		lazy[p]=0;
		return;
	}
	MID;
	pushdown(p,l,r);
	if(ql<=mid) cover(ls,l,mid,ql,qr,w);
	if(qr>mid) cover(rs,mid+1,r,ql,qr,w);
	tr[p]=max(tr[ls],tr[rs]); 
}

void add(int p,int l,int r,int ql,int qr,int w){
	if(ql<=l&&r<=qr){
		tr[p]+=w;
		lazy[p]+=w;
		return;
	}
	MID;
	pushdown(p,l,r);
	if(ql<=mid) add(ls,l,mid,ql,qr,w);
	if(qr>mid) add(rs,mid+1,r,ql,qr,w);
	tr[p]=max(tr[ls],tr[rs]);
}

int query(int p,int l,int r,int ql,int qr){
	if(ql<=l&&r<=qr) return tr[p];
	MID;int res=0;
	pushdown(p,l,r);
	if(ql<=mid) res=max(res,query(ls,l,mid,ql,qr));
	if(qr>mid) res=max(res,query(rs,mid+1,r,ql,qr));
	return res;
}

int n,u,v,w,rt;
string str;

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
  	//freopen("in.cpp","r",stdin);
  	//freopen("out.cpp","w",stout);
	cin>>n;
	for(int i=1;i<n;i++){
		cin>>u>>v>>w;
		addedge(u,v,w);
		bk[i]=v;
	}
	for(int i=1;i<=n;i++) if(bk[i]) rt=i;
	dfs1(rt);
	dfs2(rt,rt);
	build(1,1,n);
	while(cin>>str){
		if(str=="Stop") break;
		if(str=="Max"){
			cin>>u>>v;
			int res=0;
			while(bel[u]!=bel[v]){
				if(dep[bel[u]]<dep[bel[v]]) swap(u,v);
				res=max(res,query(1,1,n,dfn[bel[u]],dfn[u]));
				u=fa[bel[u]];
			}
			if(dep[u]<dep[v]) swap(u,v);
			if(u!=v) res=max(res,query(1,1,n,dfn[v]+1,dfn[u]));
			cout<<res<<"\n";
		}
		if(str=="Add"){
			cin>>u>>v>>w;
			while(bel[u]!=bel[v]){
				if(dep[bel[u]]<dep[bel[v]]) swap(u,v);
				add(1,1,n,dfn[bel[u]],dfn[u],w);
				u=fa[bel[u]];
			}
			if(dep[u]<dep[v]) swap(u,v);
			if(u!=v) add(1,1,n,dfn[v]+1,dfn[u],w);	
		}
		if(str=="Change"){
			cin>>u>>v;
			u=dep[e[u*2-1].v]<dep[e[u<<1].v]?e[u<<1].v:e[u*2-1].v;
			cover(1,1,n,dfn[u],dfn[u],v);	
		}
		if(str=="Cover"){
			cin>>u>>v>>w;
			while(bel[u]!=bel[v]){
				if(dep[bel[u]]<dep[bel[v]]) swap(u,v);
				cover(1,1,n,dfn[bel[u]],dfn[u],w);
				u=fa[bel[u]];
			}
			if(dep[u]<dep[v]) swap(u,v);
			if(u!=v) cover(1,1,n,dfn[v]+1,dfn[u],w);	
		}
	}
	return 0;
}
luoguP3833 [SHOI2012]魔法树
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define int long long
using namespace std;
const int N=2e5+7;
const int mod=1e9+7;

//int read(){	int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-') f=f*-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;}
vector<int>G[N];
inline void addedge(int u,int v){
	G[u].push_back(v);
	G[v].push_back(u);
}

int sz[N],dep[N],fa[N],son[N],bel[N],dfn[N],idx;
inline void dfs1(int x){
	sz[x]=1;
	for(int i=0;i<G[x].size();i++){
		int to=G[x][i];
		if(to==fa[x]) continue;
		fa[to]=x;
		dep[to]=dep[x]+1;
		dfs1(to);
		sz[x]+=sz[to];
		if(sz[son[x]]<sz[to]) son[x]=to; 
	}
}

inline void dfs2(int x,int tp){
	dfn[x]=++idx;
	bel[x]=tp;
	if(son[x]) dfs2(son[x],tp);
	for(int i=0;i<G[x].size();i++){
		int to=G[x][i];
		if(to==son[x]||to==fa[x]) continue;
		dfs2(to,to);
	}
}

#define MID int mid=l+r>>1;
#define ls p<<1
#define rs p<<1|1
int tr[N<<2],lazy[N<<2];

void pushdown(int p,int l,int r){
	if(lazy[p]){
		MID;
		lazy[ls]+=lazy[p];
		lazy[rs]+=lazy[p];
		tr[ls]+=(mid-l+1)*lazy[p];
		tr[rs]+=(r-mid)*lazy[p];
		lazy[p]=0;
	}
}

void update(int p,int l,int r,int ql,int qr,int val){
	if(ql<=l&&r<=qr){
		lazy[p]+=val;
		tr[p]+=(r-l+1)*val;
		return;
	}
	MID;
	pushdown(p,l,r);
	if(ql<=mid) update(ls,l,mid,ql,qr,val);
	if(qr>mid) update(rs,mid+1,r,ql,qr,val);
	tr[p]=tr[ls]+tr[rs];
}

int query(int p,int l,int r,int ql,int qr){
	if(ql<=l&&r<=qr) return tr[p];
	MID;int res=0;
	pushdown(p,l,r);
	if(ql<=mid) res+=query(ls,l,mid,ql,qr);
	if(qr>mid) res+=query(rs,mid+1,r,ql,qr);
	return res;
}

int n,u,v,q,d;
char op;

signed main(){
//	ios::sync_with_stdio(0);//	cin.tie(0);cout.tie(0);
//  freopen("in.cpp","r",stdin);freopen("out.cpp","w",stout);
	cin>>n;
	for(int i=1;i<n;i++){
		cin>>u>>v;
		addedge(u+1,v+1);
	}
	dfs1(1);
	dfs2(1,1);
	cin>>q;
	while(q--){
		cin>>op;
		if(op=='A'){
			cin>>u>>v>>d;
			u+=1;v+=1;
			while(bel[u]!=bel[v]){
				if(dep[bel[u]]<dep[bel[v]]) swap(u,v);
				update(1,1,n,dfn[bel[u]],dfn[u],d);
				u=fa[bel[u]];
			}
			if(dep[u]<dep[v]) swap(u,v);
			update(1,1,n,dfn[v],dfn[u],d);
		}else{
			cin>>u;
			u+=1;
			cout<<query(1,1,n,dfn[u],dfn[u]+sz[u]-1)<<"\n";
		}
	}
	return 0;
}

参考资料

https://www.bilibili.com/video/BV1Qt411u77f?from=search&seid=6685906805699952409

https://www.bilibili.com/video/BV1RT4y1L7Sb

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

RWLinno

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值