ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

树链剖分解析

2021-03-06 23:00:53  阅读:175  来源: 互联网

标签:dep 解析 剖分 int pos tp 树链 next now


原文链接

前置知识

线段树 a n d and and 树上基本操作

定义

几个在树链剖分很重要的概念。

重儿子

对于一个父节点,含有节点数最多的儿子称为重儿子。但重儿子只有一个,若满足条件的儿子有多个,则指定其中任意一个儿子为重儿子。

轻儿子

对于一个父节点,除了重儿子以为,其余的都称为轻儿子。

重边

由父节点与重儿子构成的边。

轻边

由父节点与轻儿子构成的边。

重链

由重边构成的链。

轻链

由轻边构成的链。

链顶

重链中深度最小的边为该重链的链顶。

上述几个概念具体如下图:

在这里插入图片描述
其中,黄色点为重儿子,蓝色点为轻儿子( 1 1 1 除外)。黄色边为重边,蓝色边为轻边。通常,一个单独的点也看为一条重链,那么重链有 5 5 5 条:

  • { 1 , 2 , 6 , 7 } \{ 1,2,6,7\} {1,2,6,7} ,( 1 1 1 为链顶)
  • { 5 } \{ 5\} {5} ,( 5 5 5 为链顶)
  • { 3 } \{ 3\} {3} ,( 3 3 3 为链顶)
  • { 4 , 8 } \{ 4,8\} {4,8} ,( 4 4 4 为链顶)
  • { 9 } \{ 9\} {9} ,( 9 9 9 为链顶)

对于任意一棵树有如下性质:从任意一点到根节点的简单路径上,共有不超过 l o g 2 ( n ) log2(n) log2(n) 条轻链,有不超过 l o g 2 ( n ) log2(n) log2(n) 条轻链。

预处理

预处理需要使用到两个 d f s dfs dfs 。

d f s 1 dfs1 dfs1 需要处理:

  • f a [ i ] fa[i] fa[i] : i i i 节点的父亲。
  • s o n [ i ] son[i] son[i] : i i i 节点的重儿子。
  • s z [ i ] sz[i] sz[i] :以 i i i 为根节点的子树的大小,可以进一步处理 s o n [ i ] son[i] son[i] 。
  • d e p [ i ] dep[i] dep[i] : i i i 节点的深度。

d f s 2 dfs2 dfs2 需要处理:

  • d f n [ i ] dfn[i] dfn[i] : i i i 节点的时间戳,但优先遍历重儿子。显然,在一条重链中,时间戳为一段连续的数字。
  • t p [ i ] tp[i] tp[i] : i i i 节点的链顶。

C++代码

void dfs1(int now, int father) {
	fa[now] = father;//初始化父节点
	sz[now] = 1;//子树大小包括自己
	dep[now] = dep[father] + 1;//初始化深度
	int SIZ = v[now].size();
	int maxn = 0;//记录最大的子树大小
	for(int i = 0; i < SIZ; i++) {
		int next = v[now][i];
		if(next == father)
			continue;
		dfs1(next, now);//遍历这棵树
		sz[now] += sz[next];
		if(maxn < sz[next]) {//更新子节点
			maxn = sz[next];
			son[now] = next;
		}
	}
}
void dfs2(int now, int Top) {
	tp[now] = Top;//初始化链顶
	if(son[now])
		dfs2(son[now], Top);//优先遍历重儿子
	int SIZ = v[now].size();
	for(int i = 0; i < SIZ; i++) {
		int next = v[now][i];
		if(next == fa[now] || next == son[now])
			continue;
		dfs2(next, next);//继续遍历这棵树
	}
}

树链剖分求LCA

测验链接。

在线查询一棵树上任意两点的 L C A LCA LCA 。

分两种情况向上爬即可(需要保证 d e p [ t p [ x ] ] < = d e p [ t p [ y ] ] dep[tp[x]] <= dep[tp[y]] dep[tp[x]]<=dep[tp[y]]):

  1. t p [ x ] ! = t p [ y ] tp[x]!=tp[y] tp[x]!=tp[y] ,即不在一条重链上。需要 x x x 向上爬出这条重链,向上继续寻找能与 y y y 汇合的重链点。 x = f a [ t p [ x ] ] x=fa[tp[x]] x=fa[tp[x]]。
  2. t p [ x ] ! = t p [ y ] tp[x]!=tp[y] tp[x]!=tp[y] ,即在一条重链上,那么深度小的就位最近公共祖先。

正确性显然,以为两条重链不会交于同一个点。

C++代码

#include <cstdio>
#include <vector>
using namespace std;
const int MAXN = 5e5 + 5;
vector<int> v[MAXN];//vector存图
int fa[MAXN], son[MAXN], tp[MAXN], sz[MAXN], dep[MAXN];
int n, m, s;
void dfs1(int now, int father) {//初始化如上
	fa[now] = father;
	sz[now] = 1;
	dep[now] = dep[father] + 1;
	int SIZ = v[now].size();
	int maxn = 0;
	for(int i = 0; i < SIZ; i++) {
		int next = v[now][i];
		if(next == father)
			continue;
		dfs1(next, now);
		sz[now] += sz[next];
		if(maxn < sz[next]) {
			maxn = sz[next];
			son[now] = next;
		}
	}
}
void dfs2(int now, int Top) {//初始化如上
	tp[now] = Top;
	if(son[now])
		dfs2(son[now], Top);
	int SIZ = v[now].size();
	for(int i = 0; i < SIZ; i++) {
		int next = v[now][i];
		if(next == fa[now] || next == son[now])
			continue;
		dfs2(next, next);
	}
}
int Get_LCA(int x, int y) {
	while(tp[x] != tp[y]) {
		if(dep[tp[x]] < dep[tp[y]])
			swap(x, y);
		x = fa[tp[x]];
	}
	if(x == y)
		return x;
	if(dep[x] < dep[y])
		return x;
	return y; 
}
int main() {
	int A, B;
	scanf("%d %d %d", &n, &m, &s);
	for(int i = 1; i < n; i++) {
		scanf("%d %d", &A, &B);
		v[A].push_back(B);
		v[B].push_back(A);
	}
	dfs1(s, 0);
	dfs2(s, s);
	for(int i = 1; i <= m; i++) {
		scanf("%d %d", &A, &B);
		printf("%d\n", Get_LCA(A, B));
	}
	return 0;
}

例题

树链剖分通常结合着一些数据结构来进行操作,以为重链的 d f n dfn dfn 为连续的序列。

题目链接。

题目大意

对于一棵树,有 5 5 5 中操作,根据要求完成操作。

  • C i w 将输入的第 i i i 条边权值改为 w w w 。
  • N u v 将 u , v u,v u,v 节点之间的边权都变为相反数。
  • SUM u v 询问 u , v u,v u,v 节点之间边权和。
  • MAX u v 询问 u , v u,v u,v 节点之间边权最大值。
  • MIN u v 询问 u , v u,v u,v 节点之间边权最小值。

思路

先考虑第二个操作。

设 l c a lca lca 为 u , v u,v u,v 的最近公共祖先,那么可以将操作二分解为从 u u u 到 l c a lca lca 的路径取反,和将从 v v v 到 l c a lca lca 的路径取反。

那么按照上述 L C A LCA LCA 往上爬的过程刚好就可以遍历完这条路径一次。

按照点的 d f n dfn dfn 建造一颗线段树,来维护点的信息。

这里点的信息是指:这个点与它的父节点的连边的信息。

线段树需要维护的信息有:最大值,最小值,区间和。

具体的操作二代码如下:

void Negate(int pos, int l, int r) {
	if(l <= L(pos) && R(pos) <= r) {
		A(pos) = -A(pos);
		I(pos) = -I(pos);
		swap(A(pos), I(pos));//最大值取反,最小值取反,最大值边最小值
		S(pos) = -S(pos);//区间和取反
		M(pos) ^= 1;//取反两次后就相当于不取反。
		return;
	}
	Push_Down(pos);//传递懒标记
	if(l <= R(LC(pos)))
		Negate(LC(pos), l, r);
	if(r >= L(RC(pos)))
		Negate(RC(pos), l, r);
	Push_Up(pos);//修改后更新点的信息
}
void Negatepast(int x, int y) {
	while(tp[x] != tp[y]) {//不同重链向上爬
		if(dep[tp[x]] < dep[tp[y]])
			swap(x, y);
		Negate(1, dfn[tp[x]], dfn[x]);//同一条重链dfn是连续的,线段树维护
		x = fa[tp[x]];//向上爬
	}
	if(x == y)
		return;
	if(dep[x] < dep[y])
		swap(x, y);
	Negate(1, dfn[son[y]], dfn[x]);//注意是son[y],y与其父节点的连边并不需要修改
}

查询操作与其类似,就不一一列举了。

对于修改权值,使用深度较大的子节点,直接在线段树上该就好了。

C++代码

#include <cstdio>
#include <string>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
#define INF 0x3f3f3f3f
const int MAXN = 2e5 + 5;
struct Segment_Tree {//线段树
	int Left_Section, Right_Section;
	int Max_Data, Min_Data, Sum_Data, Lazy_Mark;
	#define LC(x) (x << 1)
	#define RC(x) (x << 1 | 1)
	#define L(x) Tree[x].Left_Section//左区间
	#define R(x) Tree[x].Right_Section//右区间
	#define I(x) Tree[x].Min_Data//区间最小值
	#define A(x) Tree[x].Max_Data//区间最大值
	#define S(x) Tree[x].Sum_Data//区间和
	#define M(x) Tree[x].Lazy_Mark//懒惰标记(延迟标记)
};
Segment_Tree Tree[MAXN << 2];
vector<int> v[MAXN];//vector存图
int fa[MAXN], son[MAXN], dep[MAXN], siz[MAXN];
int tp[MAXN], dfn[MAXN];
int s[MAXN], t[MAXN], w[MAXN];
int n, q;
int tim;
void Push_Up(int pos) {//更新节点信息
	A(pos) = max(A(LC(pos)), A(RC(pos)));
	I(pos) = min(I(LC(pos)), I(RC(pos)));
	S(pos) = S(LC(pos)) + S(RC(pos));
}
void Push_Down(int pos) {//传递懒标记
	if(M(pos)) {
		I(LC(pos)) = -I(LC(pos));
		A(LC(pos)) = -A(LC(pos));
		swap(I(LC(pos)), A(LC(pos)));
		S(LC(pos)) = -S(LC(pos));
		M(LC(pos)) ^= 1;
		I(RC(pos)) = -I(RC(pos));
		A(RC(pos)) = -A(RC(pos));
		swap(I(RC(pos)), A(RC(pos)));
		S(RC(pos)) = -S(RC(pos));
		M(RC(pos)) ^= 1;
		M(pos) = 0;
	}
}
void Build(int pos, int l, int r) {//初始化建树
	L(pos) = l;
	R(pos) = r;
	if(l == r)
		return;
	int mid = (l + r) >> 1;
	Build(LC(pos), l, mid);
	Build(RC(pos), mid + 1, r); 
}
void Negate(int pos, int l, int r) {//取反操作
	if(l <= L(pos) && R(pos) <= r) {
		I(pos) = -I(pos);
		A(pos) = -A(pos);
		swap(I(pos), A(pos));
		S(pos) = -S(pos);
		M(pos) ^= 1;
		return;
	}
	Push_Down(pos);
	if(l <= R(LC(pos)))
		Negate(LC(pos), l, r);
	if(r >= L(RC(pos)))
		Negate(RC(pos), l, r);
	Push_Up(pos);
}
void Negatepast(int x, int y) {
	while(tp[x] != tp[y]) {
		if(dep[tp[x]] < dep[tp[y]])
			swap(x, y);
		Negate(1, dfn[tp[x]], dfn[x]);
		x = fa[tp[x]];
	}
	if(x == y)
		return;
	if(dep[x] < dep[y])
		swap(x, y);
	Negate(1, dfn[son[y]], dfn[x]);
}
void Change(int pos, int x, int c) {//单点修改
	if(L(pos) == R(pos)) {
		I(pos) = c;
		A(pos) = c;
		S(pos) = c;
		return;
	}
	Push_Down(pos);
	if(x <= R(LC(pos)))
		Change(LC(pos), x, c);
	else
		Change(RC(pos), x, c);
	Push_Up(pos);
}
int Query_Sum(int pos, int l, int r) {//查询最大值操作,与修改类似,都已同样方向爬
	if(l <= L(pos) && R(pos) <= r)
		return S(pos);
	Push_Down(pos);
	int res = 0;
	if(l <= R(LC(pos)))
		res += Query_Sum(LC(pos), l, r);
	if(r >= L(RC(pos)))
		res += Query_Sum(RC(pos), l, r);
	return res;
}
int Sumpast(int x, int y) {
	int res = 0;
	while(tp[x] != tp[y]) {
		if(dep[tp[x]] < dep[tp[y]])
			swap(x, y);
		res += Query_Sum(1, dfn[tp[x]], dfn[x]);
		x = fa[tp[x]];
	}
	if(x == y)
		return res;
	if(dep[x] < dep[y])
		swap(x, y);
	res += Query_Sum(1, dfn[son[y]], dfn[x]);
	return res;
}
int Query_Min(int pos, int l, int r) {//查询最小值
	if(l <= L(pos) && R(pos) <= r)
		return I(pos);
	Push_Down(pos);
	int res = INF;
	if(l <= R(LC(pos)))
		res = min(res, Query_Min(LC(pos), l, r));
	if(r >= L(RC(pos)))
		res = min(res, Query_Min(RC(pos), l, r));
	return res;
}
int Minpast(int x, int y) {
	int res = INF;
	while(tp[x] != tp[y]) {
		if(dep[tp[x]] < dep[tp[y]])
			swap(x, y);
		res = min(res, Query_Min(1, dfn[tp[x]], dfn[x]));
		x = fa[tp[x]];
	}
	if(x == y)
		return res;
	if(dep[x] < dep[y])
		swap(x, y);
	res = min(res, Query_Min(1, dfn[son[y]], dfn[x]));
	return res;
}
int Query_Max(int pos, int l, int r) {//查询最大值
	if(l <= L(pos) && R(pos) <= r)
		return A(pos);
	Push_Down(pos);
	int res = -INF;
	if(l <= R(LC(pos)))
		res = max(res, Query_Max(LC(pos), l, r));
	if(r >= L(RC(pos)))
		res = max(res, Query_Max(RC(pos), l, r));
	return res;
}
int Maxpast(int x, int y) {
	int res = -INF;
	while(tp[x] != tp[y]) {
		if(dep[tp[x]] < dep[tp[y]])
			swap(x, y);
		res = max(res, Query_Max(1, dfn[tp[x]], dfn[x]));
		x = fa[tp[x]];
	}
	if(x == y)
		return res;
	if(dep[x] < dep[y])
		swap(x, y);
	res = max(res, Query_Max(1, dfn[son[y]], dfn[x]));
	return res;
}
void dfs1(int now, int father) {//初始化
	dep[now] = dep[father] + 1;
	siz[now] = 1;
	fa[now] = father;
	int SIZ = v[now].size();
	int maxn = 0;
	for(int i = 0; i < SIZ; i++) {
		int next = v[now][i];
		if(next == fa[now])
			continue;
		dfs1(next, now);
		siz[now] += siz[next];
		if(maxn < siz[next]) {
			maxn = siz[next];
			son[now] = next;
		}
	}
}
void dfs2(int now, int Top) {//初始化
	tp[now] = Top;
	dfn[now] = ++tim;
	if(son[now])
		dfs2(son[now], Top);
	int SIZ = v[now].size();
	for(int i = 0; i < SIZ; i++) {
		int next = v[now][i];
		if(son[now] == next || fa[now] == next)
			continue;
		dfs2(next, next);
	}
}
int main() {
	scanf("%d", &n);
	for(int i = 1; i < n; i++) {
		scanf("%d %d %d", &s[i], &t[i], &w[i]);
		s[i]++;
		t[i]++;
		v[s[i]].push_back(t[i]);
		v[t[i]].push_back(s[i]);
	}
	dfs1(1, 0);
	dfs2(1, 1);
	Build(1, 1, n);
	for(int i = 1; i < n; i++) {
		if(dep[s[i]] < dep[t[i]])//深度小的一定就是子节点
			swap(s[i], t[i]);
		Change(1, dfn[s[i]], w[i]);//初始化线段树
	}
	scanf("%d", &q);
	string opt;
	int a, b;
	while(q--) {
		cin >> opt;
		scanf("%d %d", &a, &b);
		a++;
		b++;
		if(opt[0] == 'N')
			Negatepast(a, b);
		else if(opt[0] == 'C')
			Change(1, dfn[s[a - 1]], b - 1);
		else if(opt[0] == 'S')
			printf("%d\n", Sumpast(a, b));
		else if(opt[1] == 'A')
			printf("%d\n", Maxpast(a, b));
		else
			printf("%d\n", Minpast(a, b));
	}
	return 0;
}

标签:dep,解析,剖分,int,pos,tp,树链,next,now
来源: https://blog.csdn.net/Face_the_Blast/article/details/114457722

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有