首页 > 其他分享 >树链剖分

树链剖分

时间:2024-04-25 13:22:45浏览次数:30  
标签:son 剖分 int fy 树链 dfn 重链 节点

link1
link2

前言:

树链剖分实际上就是一种将树形结构剖分成一条条链状结构,并用线性数据结构来快速维护信息。

重链剖分:

一些定义:

  1. 重儿子:一个节点的重儿子定义为它的子节点中子树节点最大的节点。

  2. 轻儿子:一个节点除重儿子外的所有儿子

  3. 重边:一个节点到它的重儿子的边即为重边

  4. 轻边:一个节点到它的轻儿子的边即为轻边

  5. 重链:一条所有都由重边组成的链即为重链(特别的,一个落单的节点也算一条重链)

一些性质:

  • 一棵树所有的节点都属于且仅属于一条重链,所有的重链将整棵树完全剖分

  • 每次沿着一条轻边往下走,所在子树大小至少除以二

  • 对于每条路径,都可以剖分成不超过 \(\log n\) 条重链。

剖分过程:

先给出一些需要用到的变量定义

\(fa[u]\): 表示节点 \(u\) 的父亲编号。

\(siz[u]\):表示节点 \(u\) 的子树大小。

\(dep[u]\): 表示节点 \(u\) 的深度。

\(son[u]\): 表示节点 \(u\) 的重儿子编号。

\(top[u]\): 表示节点 \(u\) 所在重链的顶端。

\(dfn[u]\):表示节点 \(u\) 的 \(\rm dfs\) 序,也是节点 \(u\) 在数据结构中的编号

\(rev[u]\):表示 \(\rm dfs\) 序为 \(u\) 的节点编号,有 \(rev[dfn[u]]=u\)

我们可以通过两次 \(\rm dfs\) 来求出这些信息。两次分别求出前四者和后三者。

第一次 \(\dfs\) 比较简单,给出代码:

void dfs1(int u,int father){
	siz[u]=1;
	fa[u]=father;
	dep[u]=dep[father]+1;
	int maxsiz=0;
	for(int i=head[u];i;i=edges[i].next){
		int v=edges[i].v;
		if(v==father)continue;
		dfs1(v,u);
		siz[u]+=siz[v];
		if(siz[v]>maxsiz){
			maxsiz=siz[v];
			son[u]=v;
		}
	}
	return;
}

关键是第二次 \(\rm dfs\)。第二次 \(\rm dfs\) 需要保证重链中节点的 \(dfn\) 值连续。所以我们要优先走重儿子。细节见代码:

void dfs2(int u,int t){//t 是重链链顶
	top[u]=t;
	dfn[u]=++dfncnt;
	rev[dfn[u]]=u;
	if(!son[u])return;// 注意这里
	dfs2(son[u],t);// 因为走的是重边,所以链顶不变
	for(int i=head[u];i;i=edges[i].next){
		int v=edges[i].v;
		if(v==fa[u]||v==son[u])continue;
		dfs2(v,v);// 这里走的是轻边,导致链断开了
	}
	return;
}

此时我们就完成了重链剖分的过程了。

拿一道题来举例:P2590 [ZJOI2008] 树的统计

我们考虑对 \(x,y\) 两点求 LCA 的过程。

记 \(fx\) 是节点 \(x\) 的重链链顶,\(fy\) 同理。

此时我们选取两者链顶深度较大的向上跳到链顶的父亲处,并同时将这条链的贡献记入答案,显然由于 \(dfn\) 序连续,可以直接用线段树来维护。

本题代码如下:

#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=5e4+10,INF=0x3f3f3f3f;
struct edge{
	int v,next;
}edges[N*2];
int head[N],idx;
int n,w[N];
int siz[N],top[N],dep[N],son[N],fa[N],dfn[N],rev[N];

void add_edge(int u,int v){
	idx++;
	edges[idx].v=v;
	edges[idx].next=head[u];
    head[u]=idx;
	return;
}// 链式前向星加边

struct Segment_tree{
	#define ls (o<<1)
	#define rs (o<<1|1)
	#define mid (l+r>>1)
	int tsum[N<<2],tmax[N<<2];
	void pushup(int o){
		tsum[o]=tsum[ls]+tsum[rs];
		tmax[o]=max(tmax[ls],tmax[rs]);
		return;
	}
	void build(int o,int l,int r){
		if(l==r){
			tsum[o]=tmax[o]=w[rev[l]];
			return;
		}
		build(ls,l,mid);
		build(rs,mid+1,r);
		pushup(o);
		return;
	}// 建树
	void modify(int o,int l,int r,int id,int k){
		if(l==r){
			tsum[o]=tmax[o]=k;
			return;
		}
		if(id<=mid)modify(ls,l,mid,id,k);
		else modify(rs,mid+1,r,id,k);
		pushup(o);
		return;
	}// 单点更新
	int querymax(int o,int l,int r,int s,int t){
		if(s<=l&&r<=t)return tmax[o];
		int ret=-INF;
		if(s<=mid)ret=querymax(ls,l,mid,s,t);
		if(mid<t)ret=max(ret,querymax(rs,mid+1,r,s,t));
		return ret;
	}// 求区间最大值
	int querysum(int o,int l,int r,int s,int t){
		if(s<=l&&r<=t)return tsum[o];
		int ret=0;
		if(s<=mid)ret=querysum(ls,l,mid,s,t);
		if(mid<t)ret+=querysum(rs,mid+1,r,s,t);
		return ret;
	}// 求区间和
}tree;

void dfs1(int u,int father){
	siz[u]=1;
	fa[u]=father;
	dep[u]=dep[father]+1;
	int maxsiz=0;
	for(int i=head[u];i;i=edges[i].next){
		int v=edges[i].v;
		if(v==father)continue;
		dfs1(v,u);
		siz[u]+=siz[v];
		if(siz[v]>maxsiz){
			maxsiz=siz[v];
			son[u]=v;
		}
	}
	return;
}// 第一次 dfs
int dfncnt;
void dfs2(int u,int t){
	top[u]=t;
	dfn[u]=++dfncnt;
	rev[dfn[u]]=u;
	if(!son[u])return;
	dfs2(son[u],t);
	for(int i=head[u];i;i=edges[i].next){
		int v=edges[i].v;
		if(v==fa[u]||v==son[u])continue;
		dfs2(v,v);
	}
	return;
}// 第二次 dfs

int Qsum(int x,int y){
	int ret=0;
	int fx=top[x],fy=top[y];
	while(fx!=fy){
		if(dep[fx]>=dep[fy]){
			ret+=tree.querysum(1,1,n,dfn[fx],dfn[x]);// 注意不要写反了
			x=fa[fx];// 向上跳要多跳一个节点
		}
		else{
			ret+=tree.querysum(1,1,n,dfn[fy],dfn[y]);
			y=fa[fy];
		}
		fx=top[x];fy=top[y];// 记得更新
	}
	if(dfn[x]<dfn[y])ret+=tree.querysum(1,1,n,dfn[x],dfn[y]);// 别忘了最后还要加上两点间的部分链
	else ret+=tree.querysum(1,1,n,dfn[y],dfn[x]);
	return ret;
}

int Qmax(int x,int y){
	int ret=-INF;
	int fx=top[x],fy=top[y];
	while(fx!=fy){
		if(dep[fx]>=dep[fy]){
			ret=max(ret,tree.querymax(1,1,n,dfn[fx],dfn[x]));
			x=fa[fx];
		}
		else{
			ret=max(ret,tree.querymax(1,1,n,dfn[fy],dfn[y]));
			y=fa[fy];
		}
		fx=top[x];fy=top[y];
	}
	if(dfn[x]<dfn[y])ret=max(ret,tree.querymax(1,1,n,dfn[x],dfn[y]));
	else ret=max(ret,tree.querymax(1,1,n,dfn[y],dfn[x]));
	return ret;
}

signed main(){
	std::ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin>>n;
	for(int i=1;i<n;i++){
		int x,y;
		cin>>x>>y;
		add_edge(x,y);
		add_edge(y,x);
	}
	for(int i=1;i<=n;i++)cin>>w[i];
	dfs1(1,0);
	dfs2(1,1);
	tree.build(1,1,n);
	int T;cin>>T;
	while(T--){
		string opt;
		int x,y;
		cin>>opt>>x>>y;
		if(opt=="CHANGE")tree.modify(1,1,n,dfn[x],y);
		if(opt=="QSUM")cout<<Qsum(x,y)<<"\n";
		if(opt=="QMAX")cout<<Qmax(x,y)<<"\n";
	}
	
	return 0;
}

标签:son,剖分,int,fy,树链,dfn,重链,节点
From: https://www.cnblogs.com/little-corn/p/18157458

相关文章

  • 树链剖分
    参考链接:https://www.cnblogs.com/dx123/p/16320467.html(感谢董晓老师)树链剖分求LCA比倍增快一些https://www.luogu.com.cn/problem/P3379#include<bits/stdc++.h>constintN=5e5+5;usingnamespacestd;intn,m,root;inth[N],ne[2*N],e[2*N],idx;intdep[N]......
  • 树链剖分lca笔记
    树链剖分求lca参考资料:https://www.cnblogs.com/cangT-Tlan/p/8846408.html[树链剖分lca]大佬讲的很清楚%%%orz这篇博客是我的学习笔记。树链剖分:树链剖分,计算机术语,指一种对树进行划分的算法,它先通过轻重边剖分将树分为多条链,保证每个点属于且只属于一条链,然后再通过数据结......
  • 「树链剖分」 学习笔记
    一,树链剖分的思想与概述正如其名,树链剖分用于将树剖分成若干条链的形式,以维护树上路径的信息,其中剖分出的链有多种形式,最常见的是重链,还有长链或更多其它的链。其中剖分出的链为重链时,就引出了下文的主角「重链剖分」。重链剖分能保证划分出的每条重链上的节点DFS序连续,因此......
  • 树链剖分 学习笔记
    随便写一点。1.原理定义重儿子为子树内子树大小最大的任一个点,重边为重儿子向其父亲连的边,其余为轻边。根据定义,轻边的父亲的子树大小一定不小于这个点的子树大小的二倍。又可以证出重边数量是\(O(\logn)\)的。因此可以用线段树维护这个东西。2.应用2.1dsu2.2lca考......
  • 【学习笔记】树链剖分
    一、重链剖分1.定义&作用树链剖分用于解决在树上执行的操作,将树上操作变为区间操作。用区间来维护。2.主要思想&实现有一棵树然后,我们把边区分一下,有一些边是“重边”,其余的是“轻边”,像这样:(红边为重边,黑边为轻)重边和轻边如何确定呢?看每一个结点旁的红色数字,代表他......
  • 1039. 多边形三角剖分的最低得分
    题目链接:实现一、记忆化搜索classSolution{public:intminScoreTriangulation(vector<int>&values){intn=values.size();intmemo[n][n];memset(memo,-1,sizeofmemo);//-1表示还没有计算过function<int(int,int)>df......
  • 重链剖分学习笔记
    Part.1引入重链剖分是一种用于解决树上的路径查询和修改问题的算法,他能将\(O(n)\)级别的操作转换为\(O(\logn)\)的级别,可以说十分常用。本文将带你深入解析这个算法。Part.2概念和思路在讲解本算法之前,我们先引入一些概念:轻重儿子:对于一个树上的节点\(u\),其儿子中子......
  • 树链剖分
    前言打算好好写一篇文章。至于为什么选了树剖这个主题,一是因为不久前学了长剖求树上k级祖先,二是因为重剖是我进提前批以来第一个学习到的树上算法,再加上有学弟写的超级详细的树剖学习笔记,也会使我写起来比较轻松一点。同时也可以熟悉一下TikZ这个宏包,它的功能实在是太多了,......
  • P8025 【树链剖分求祖先】
    P8025【树链剖分求祖先】这题的题意简单,简单分类讨论一下这里就不赘述了。最后题目就简化成求一个点的\(k\)级祖先。开始会的解法是倍增,但是常数过高被卡了。常数更优秀的做法是树剖,每一次跳树链,最后可能有一条链太长只能跳一部分,这是因为树链剖分的\(dfn\)序是有序的,即每......
  • P3384 【模板】重链剖分/树链剖分
    原题链接题解dalao‘sblog我自己的认识请看代码区code#include<bits/stdc++.h>usingnamespacestd;intn,Q,root,mod;intbigson[100005];//和自己在同一条链上的儿子节点vector<int>G[100005];intsizes[100005];//主要是为了求子树大小,经过验证选择哪一个儿子节点......