树链剖分

树链剖分是个好东西呀
我挺喜欢用树剖求\(LCA\)
因为我不太会打倍增

接下来就稍微说一下树剖的实现
首先我们先引入几个概念:

  • 重儿子:这个点的子树中\(siz\)最大的
  • 轻儿子:子节点中除了重儿子的节点都是轻儿子
  • 重边:与重儿子相连的边
  • 轻边:与轻儿子相连的边
  • 重链:重边组成的链叫重链
    然后我们我还要知道跳重链是个什么东东

就以这张我在网上扒的图片为例吧
这张图里的所有黑色加粗的边都是重边
我们用\(top\)记录一下当前这条重链的顶端就好了
例如:\(14\)那条重链的顶端就是\(1\),而\(11\)那条重链的顶端就是\(2\)
然后我们就可以根据这个来进行操作了

\(LCA\)

\(LCA\)的主要思路就是跳重链,直到两个点跳到同一条重链上为止,并且深度较小的那个就是要求的\(LCA\),我们可以模拟求一下\(12\)\(7\)\(LCA\)
首先我们可以知道\(7\)\(12\)并不在同一条重链上,所以我们要通过跳重链来使这两个点跳到一条重链上去
我们要让所处链的\(top\)深的先跳,直到跳到与那个浅的\(top\)同高或比它浅为止
我们就可以看出我们就要让\(12\)先跳到\(2->6->11\)这条重链上去(即跳到\(fa[top]\)上去),然后我们就可以在让\(12\)向上跳,我们就会发现,
它会跳到了\(1\)的位置,然后我们就可以让\(7\)跳了,\(7\)跳一次重链就到达了\(1\),然后就可以得出结论\(7\)\(12\)\(LCA\)就是\(1\)
我们简化为:

\[12->6->1 \]

\[7->1 \]

然后就看一道模板题
题目
其实就是求\(LCA\)的裸题(下面会有两种代码,但是我个人比较爱写树剖的这个)

倍增版:
#include<bits/stdc++.h>
using namespace std;
const int N = 500000 + 5;
int n, m, root, head[N], cnt, hig[N], f[N][21];
struct node{
	int u, v, nxt;
}edge[N << 1];
void add(int u, int v){
	edge[++ cnt].u = u;
	edge[cnt].v = v;
	edge[cnt].nxt = head[u];
	head[u] = cnt;
}
void add_tree(int x){
	for(int i = 1; i <= 19; i ++){
		f[x][i] = f[f[x][i - 1]][i - 1];
	}
	for(int i = head[x]; i; i = edge[i].nxt){
		if(edge[i].v == f[x][0]) continue;
		f[edge[i].v][0] = x;
		hig[edge[i].v] = hig[x] + 1;
		add_tree(edge[i].v);
	}
}
int ask(int x, int y){
	if(hig[x] < hig[y]) swap(x, y);
	for(int i = 19; i >= 0; i --){
		if(hig[f[x][i]] >= hig[y]) x = f[x][i];
	}
	if(x == y) return x;
	for(int i = 19; i >= 0; i--){
		if(f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
	}
	return f[x][0];
}
int read(){
	int op = 0, opp = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){ if(ch == '-') opp = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){ op = (op << 1) + (op << 3) + (ch - '0'); ch = getchar();}
	return op * opp;
}
int main(){
	n = read(), m = read(), root = read();
	for(int i = 1, x, y; i < n; i ++){
		x = read(), y = read();
		add(x, y); add(y, x);
	}
	hig[root] = 1;
	add_tree(root);
	for(int i = 1, x, y; i <= m; i ++){
		x = read(), y = read();
		printf("%d\n", ask(x, y));
	}
	return 0;
}
树剖版
#include<bits/stdc++.h>
using namespace std;
const int N = 500000 + 5;
int n, m, root, head[N], fa[N], top[N], cnt, hig[N], siz[N], hev[N];
struct node{
	int u, v, nxt;
}edge[N << 1];
void add(int u, int v){
	edge[++ cnt].u = u;
	edge[cnt].v = v;
	edge[cnt].nxt = head[u];
	head[u] = cnt;
}
void dfs1(int x){
	siz[x] = 1;
	for(int i = head[x]; i; i = edge[i].nxt){
		int to = edge[i].v;
		if(to == fa[x]) continue;
		fa[to] = x;
		hig[to] = hig[x] + 1;
		dfs1(to);
		siz[x] += siz[to];
		if(siz[to] > siz[hev[x]])	hev[x] = to;
	}
}
void dfs2(int x, int topx){
	top[x] = topx;
	if(hev[x]) dfs2(hev[x], topx);
	for(int i = head[x]; i; i = edge[i].nxt){
		int to = edge[i].v;
		if(to == fa[x] || to == hev[x]) continue;
		dfs2(to, to);
	}
}
int ask(int x, int y){
	while(top[x] != top[y]){
		if(hig[top[x]] < hig[top[y]]) swap(x, y);
		x = fa[top[x]];
	}
	return hig[x] < hig[y] ? x : y;
}
int read(){
	int op = 0, opp = 1; char ch = getchar();
	while(ch < '0' || ch > '9'){ if(ch == '-') opp = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9'){ op = (op << 1) + (op << 3) + (ch - '0'); ch = getchar();}
	return op * opp;
}
int main(){
	n = read(), m = read(), root = read();
	for(int i = 1, x, y; i < n; i ++){
		x = read(), y = read();
		add(x, y); add(y, x);
	}
	hig[root] = 1;
	dfs1(root);
	dfs2(root, root);
	for(int i = 1, x, y; i <= m; i ++){
		x = read(), y = read();
		printf("%d\n", ask(x, y));
	}
	return 0;
}

\(LCA\)其实有很多种方法的,只不过因为\(Tethys\)太菜了,只会这两种,所以就先占个坑吧

posted @ 2020-07-29 16:05  Tethys  阅读(100)  评论(5编辑  收藏  举报