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

重链剖分

时间:2023-10-12 22:00:58浏览次数:32  
标签:初始化 剖分 fa int top dep 重链

代码思路

主体部分:

初始化,剖分链,求LCA
(也就是dfs1,dfs2,LCA三个函数)

辅助部分:

struct Point{ // 节点信息多的时候会习惯开个结构体来存 
	int dep, siz, son, top, fath;
	// 点的深度 子树大小 重儿子 所在重链的链头 父亲节点 
	// 没有重儿子则son=0
	int id, l, r; // 求lca不会用到,但实际常用的东西
	// dfs序 子树dfs编号左右界 
	void getnum1(Point fa, int f){ // dfs1的初始化 
		dep = fa.dep+1; siz = 1; fath = f;
		// 求深度 初始化字数大小 记录父亲节点 
	}
	void getnum2(int tp){ // dfs2的初始化
		id = l = r = ++tot; top = tp;
		// 初始化dfs序 记录链头 
	}
}p[N];

个人习惯,在需要对节点统计很多信息的时候,开一个结构体储存。

简化部分:

暂无
(树链剖分代码其实挺短的,而且足够优美)
(我收回这句话,下面的代码敲了1h)

注意事项:

搜重儿子前要判断u是不是叶子结点
求LCA时比较的是链头深度


原理

对树进行分块(不过是分成链)

指路详细说明


代码注释

洛谷P3379为例

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

const int N = 5e5+5; // 数据范围(节点个数) 
int n, m, s, head[N]; // 一些常规的东西 
int tot = 0; // 求lca不会用到,但实际常用的dfs序计数器 
struct Point{ // 节点信息多的时候会习惯开个结构体来存 
	int dep, siz, son, top, fath;
	// 点的深度 子树大小 重儿子 所在重链的链头 父亲节点 
	// 没有重儿子则son=0
	int id, l, r; // 求lca不会用到,但实际常用的东西
	// dfs序 子树dfs编号左右界 
	void getnum1(Point fa, int f){ // dfs1的初始化 
		dep = fa.dep+1; siz = 1; fath = f;
		// 求深度 初始化字数大小 记录父亲节点 
	}
	void getnum2(int tp){ // dfs2的初始化
		id = l = r = ++tot; top = tp;
		// 初始化dfs序 记录链头 
	}
}p[N];
struct Edge{ // 链表存边 
	int u, v;
}e[N*2]; // 双向边

void dfs1(int u, int fa){ // 第一遍深搜:求节点的各种信息 
	p[u].getnum1(p[fa], fa); // 初始化 
	int maxsiz = 0; // 最大的子·子树大小
	// ↑用于辅助求重儿子 
	for(int i = head[u]; i; i = e[i].v){ // 枚举所有边 
		int v = e[i].u; // 边的端点 
		if(v == fa) continue; // 辈分不能乱JPG 
		dfs1(v, u); // 继续往下深搜
		p[u].siz += p[v].siz; // 统计子树大小 
		if(p[v].siz > maxsiz){ // 如果这个儿子更重 
			maxsiz = p[v].siz; // 更新辅助值 
			p[u].son = v; // 更新重儿子 
		}
	}
}

void dfs2(int u, int fa, int top){ // 第二遍深搜:统计重链
	// top是u所在重链的链头 
	p[u].getnum2(top); // 初始化 
	if(p[u].son) dfs2(p[u].son, u, top); // 先搜重儿子以保证dfs序连续 
	// ↑注意要判断u是不是叶子结点
	for(int i = head[u]; i; i = e[i].v){ // 枚举所有边 
		int v = e[i].u; // 边的端点 
		if(v == fa || v == p[u].son) continue;
		// 不能再搜父亲和重儿子
		dfs2(v, u, v); // 轻儿子所在重链的链头是它自己 
	}
} 

int LCA(int u, int v){ // 求u和v的最近公共祖先 
	while(p[u].top != p[v].top){
		// 在同一重链则退出 
		if(p[p[u].top].dep < p[p[v].top].dep) swap(u, v); // 调整深度 
		// ↑注意这里比较的是链头的深度 
		u = p[p[u].top].fath; // 沿着轻边向上跳
	}
	// 这时候已经在同一个重链上了
	// 那么深度小的节点就是LCA
	if(p[u].dep < p[v].dep) swap(u, v);
	return v;  // 返回LCA 
}

int main(){ // 喜闻乐见的主函数 
	// ↓输入 
	scanf("%d%d%d", &n, &m, &s);
	for(int i = 1; i < n; i++){
		int u, v; scanf("%d%d", &u, &v);
		e[i] = (Edge){v, head[u]}; head[u] = i;
		e[i+n] = (Edge){u, head[v]}; head[v] = i+n;
	}
	// ↑输入 

	dfs1(s, 0); dfs2(s, 0, s); // 两遍dfs剖出重链 

	for(int i = 1; i <= m; i++){ // m次询问 
		int u, v; scanf("%d%d", &u, &v);
		printf("%d\n", LCA(u, v)); // 输出答案 
	}
	return 0;
} 

标签:初始化,剖分,fa,int,top,dep,重链
From: https://www.cnblogs.com/meteor2008/p/17760471.html

相关文章

  • 树链剖分【产品说明书】
    一种暴论:树链剖分=多叉树上分块+线段树适用范围总之就是数据结构的基础问题。总的来说,树链剖分可以在\(O(m\logn)\)的时间复杂度中,解决大多数树上路径问题,包括其修改、维护和查询。例如这样的一道模板题又例如……(请直接跳到本文最后一章)产品简介树链剖分有两种:重......
  • 树链剖分
    树链剖分树链剖分常用于解决树上路径查询的问题。原理:对于树上两点之间的路径\(u\)->\(v\),根据某种策略,将之拆分成若干条链,然后利用线段树等数据结构单独维护这些子链,最后将答案合并。常用的剖分方法:轻重边划分。剖分树种的边可以分为两种边:重边和轻边。设\(size_u\)......
  • 算法:树链剖分
    去年就看过树链剖分的视频了,当时连树状数组,线段树都没学,对树的dfs也一知半解,所以基本完全听不懂。昨天又重新看了一般,感觉思路挺简单,应该比线段树简单吧,从用树链剖分求LCA来看确实是这样的,但是没有想到的是用线段树维护树链剖分。QAQ这应该是我打过最长的代码吧!(3K)树链剖分......
  • 浅谈树链剖分—轻重链剖分
    闲话似乎会有很多种树剖,什么长链剖分之类的,但是暂时只会轻重链剖分(可怜)。以前的版本在这里,但是感觉写的太粗糙了,所以决定重写一篇(我也不知道为什么要一直写树剖而不写点别的)。正文引入如果给出一个序列,有一些区间修改查询之类的操作,直接上线段树什么的就完事了,但是如果给出的......
  • 树链剖分
    前言:本人太弱了,看着题解调了一下午,才A了树剖的板子题,所以写篇博客纪念一下(其实是怕自己忘了树剖怎么做)。本文以板子题为例子定义:树链剖分,计算机术语,指一种对树进行划分的算法,它先通过轻重边剖分将树分为多条链,保证每个于且只属于一条链,然后再通过数据结构(树状数组、BST、SPL......
  • [学习笔记] 树链剖分
    叫复习笔记或许更好。树链剖分就是把树剖成链去解决一些问题。定义重子节点:子节点中子树大小最大的节点。轻子节点:除重子节点外的其他子节点。重边:到重子节点的边。轻边:到轻子节点的边。记号\(dfn[x]\):DFS序,也是在线段树中的编号。\(son[x]\):重子节点。\(dep[x]\)......
  • 「学习笔记」树链剖分
    树链剖分用于将树分割成若干条链的形式,以维护树上路径的信息。具体来说,将整棵树剖分为若干条链,使它组合成线性结构,然后用其他的数据结构维护信息。树链剖分有很多种形式,本文要讲的是其中的轻重链剖分。树链剖分本质上就是把链从树上砍下来,然后放到树状数组或线段树上来维护。......
  • 长链剖分
    例题:P5903【模板】树上k级祖先题目描述思路长链剖分模板题。长链剖分:计算\(f[i][j]\)表示\(i\)的\(2^j\)级祖先;计算\(up[i][j]\)表示\(i\)的\(j\)级祖先;计算\(down[i][j]\)表示在长链上从\(i\)向下走\(j\)步到达的祖先。计算\(i\)的\(k\)级祖......
  • 树链剖分学习(复习?)笔记
    树链剖分,即树剖。顾名思义,树链剖分就是将一棵树通过某种方式剖分成若干条链,再利用\(dfs\)序,从而将树上的问题转化为序列上的问题。树剖的方式有不止一种,比如重链剖分、长链剖分。最常用的(大概?)是重链剖分。此处介绍重链剖分。首先,我们定义一个节点的重儿子为此节点的所有儿......
  • 树链剖分/重链剖分模板
    #include<bits/stdc++.h>usingnamespacestd;intn,m,d,mod,w[200005],wt[200005],e[200005],ne[200005],h[200005],idx=1,t[800005],l[800005],r[800005],tag[800005];intson[200005],id[200005],fa[200005],cnt,dep[200005],siz[200005],top[200005];/*son[]重儿子......