【模板】LCA

众所周知,\(LCA\)分为离线和在线\((\text{ST表 or 树上倍增})\)两种。其主要思想是:设,我的父辈为我的\(0\)辈祖先,以此类推,则我的\(j-1\)辈祖先的\(j-1\)辈祖先是我的\(j\)辈祖先。

#include "bits/stdc++.h"
using namespace std;
const int z = 131072;

struct EDGE {
	int t, next;
} edge[z*2];
int cnt, head[z];
void add_edge(int f,int t) {
	edge[++cnt].t = t;
	edge[cnt].next = head[f];
	head[f] = cnt;
}

正常的建边操作

\(RMQ\text{的}LCA\)

int tot, depth[z*2], first[z], dp[z][18], ver[z*2];
bool vis[z];
void dfs(int u,int d) {
	vis[u] = true;
	ver[++tot] = u;
	first[u] = tot;
	depth[tot] = d;
	for(int i = head[u];i;i = edge[i].next) 
		if(!vis[edge[i].t]) {
			dfs(edge[i].t,d+1);
			ver[++tot] = u;
			depth[tot] = d;
		}
}
void ST() {
	for(int i = 1;i <= tot;++i) 
		dp[i][0] = i;
	for(int j = 1;(1<<j) <= tot;++j) 
		for(int i = 1;i+(1<<j) <= tot;++i) {
			int a = dp[i][j-1];
			int b = dp[i+(1<<(j-1))][j-1];
			dp[i][j] = depth[a] < depth[b] ? a : b;
		}
}
int RMinQ_query(int l,int r) {
	int k = 0;
	while((1<<(k+1)) <= r-l+1) ++k;
	int a = dp[l][k];
	int b = dp[r-(1<<k)+1][k];
	return depth[a] < depth[b] ? a : b;
}
int LCA(int u,int v) {
	int x = first[u];
	int y = first[v];
	if(x > y) swap(x,y);
	int res = RMinQ_query(x,y);
	return ver[res];
}
int n, m, q;
int a, b;
void work() {
	scanf("%d %d %d",&n,&m,&q);
	for(int i = 1;i <= m;++i) {
		scanf("%d %d",&a,&b);
		add_edge(a,b);
		add_edge(b,a);
	}
	dfs(1,1);
	ST();
	for(int i = 1;i <= q;++i) {
		scanf("%d %d",&a,&b);
		printf("%d\n",LCA(a,b));
	}
}//on line LCA;

\(\text{对树进行一次}dfs\text{,每当经过一个点时,就把它的时间戳记录下来,这样形成的序列被称为这棵树的欧拉序。}\)

\(\text{树上两个点的}LCA\text{,就是欧拉序中这两个点第一次出现的位置之间}dfn\text{最小的节点。}\)

\(\text{因此可以用}RMQ\text{问题中的}ST\text{算法来维护欧拉序。}\)

\(\text{这也是一个在线算法,时间复杂度为}\operatorname{O}(n\operatorname{log}(n))\)~\(\operatorname{O}(1)\text{。}\)

\(\text{树上倍增}LCA\)

bool vis[V];
int depth[V], parent[V][18], logup;
std :: queue<int> q;
void dfs(int u,int p) {
	parent[u][0] = p;
	depth[u] = depth[p]+1;
	logup = log(depth[u])/log(2)+1;
	for(int i = 1;i <= logup;++i) 
		parent[u][i] = parent[parent[u][i-1]][i-1];
	for(int i = head[u];i;i = edge[i].next) {
		int v = edge[i].t;
		if(v == p) 
			continue;
		dfs(v,u);
	}
}
int LCA(int u,int v) {
	if(!vis[1]) 
		dfs(1,0);
	if(depth[u] > depth[v]) 
		std :: swap(u,v);
	logup = log(depth[v])/log(2)+1;
	for(int i = logup;i >= 0;--i) 
		if(depth[parent[v][i]] >= depth[u]) 
			v = parent[v][i];
	if(u == v) return u;
	logup = log(depth[v])/log(2)+1;
	for(int i = logup;i >= 0;--i) 
		if(parent[u][i] != parent[v][i]) {
			u = parent[u][i];
			v = parent[v][i];
		}
	return parent[u][0];
}
bool vis[z];
int depth[z], parent[z][18], logup;
queue<int> q;
void bfs() {
	vis[1] = depth[1] = 1;
	q.push(1);
	int u, v;
	while(!q.empty()) {
		u = q.front();
		q.pop();
		for(int i = head[u];i;i = edge[i].next) {
			v = edge[i].t;
			if(vis[v]) continue;
			depth[v] = depth[u]+1;
			vis[v] = true;
			parent[v][0] = u;
			logup = log(depth[v])/log(2)+1;
			for(int j = 1;j <= logup;++j) 
				parent[v][j] = parent[parent[v][j-1]][j-1];
			q.push(v);
		}
	}
}
int LCA(int x,int y) {
	if(depth[x] > depth[y]) swap(x,y);
	logup = log(depth[y])/log(2)+1;
	for(int i = logup;i >= 0;--i) 
		if(depth[parent[y][i]] >= depth[x]) 
			y = parent[y][i];
	if(x == y) return x;
	logup = log(depth[y])/log(2)+1;
	for(int i = logup;i >= 0;--i) 
		if(parent[x][i] != parent[y][i]) {
			x = parent[x][i];
			y = parent[y][i];
		}
	return parent[x][0];
}
int n, m, ques;
int a, b;
void work() {
	scanf("%d %d %d",&n,&m,&ques);
	for(int i = 1;i <= m;++i) {
		scanf("%d %d",&a,&b);
		add_edge(a,b);
		add_edge(b,a);
	}
	bfs();
	for(int i = 1;i <= ques;++i) {
		scanf("%d %d",&a,&b);
		printf("%d\n",LCA(a,b));
	}
}//on line LCA;

\(f_{i,j}\text{表示节点}i\text{的}2^j\text{次祖先。}\)

\(\text{则}f_{i,j}=f_{f_{(i,j-1)},j-1}\text{, 可以通过一次}bfs\text{求出}f\text{数组。}\)

\(\text{询问}lca(x,y)\text{时,假设}x\text{的深度比}y\text{大,否则交换}x,y\text{。}\)

\(\text{把}x\text{调整到与}y\text{同一深度,然后}x,y\text{再同时向上走直到汇合。}\)

\(\text{这是一个在线算法,时间复杂度为}\operatorname{O}(n\operatorname{log}(n))\)~\(\operatorname{O}(\operatorname{log}(n))\text{。}\)

\(Tarjan\text{求}LCA\)

#include <stdio.h>
const int z = 1024;
template<typename Tec>
Tec min(Tec &__x,Tec &__y) {
	return __x < __y ? __x : __y;
}
struct EDGE {
	int t, next;
} edge[z<<2];
int tot = 1, head[z];
void add_edge(int f,int t) {
	edge[++tot].next = head[f];
	edge[tot].t = t;
	head[f] = tot;
}
struct QUERY {
	int t, next;
} query[z<<2];
int qyt = 1, qtop[z];
void add_query(int u,int v) {
	query[++qyt].next = qtop[u];
	query[qyt].t = v;
	qtop[u] = qyt;
}
int p[z], col[z], ans[z];
int get(int x) {
	if(p[x] != x) 
		return get(p[x]);
	else 
		return x;
}
void tarjan(int u) {
	p[u] = u, col[u] = 1;
	for(int i = head[u];i;i = edge[i].next) {
		int v = edge[i].t;
		if(!col[v]) {
			tarjan(v);
			p[v] = u;
		}
	}
	for(int i = qtop[u];i;i = query[i].next) {
		int v = query[i].t;
		if(col[v] == 2) 
			ans[i>>1] = get(v);
	}
	col[u] = 2;
}
int n, m;
signed main() {
	scanf("%d %d",&n,&m);
	for(int i = 2, a;i <= n;++i) {
		scanf("%d",&a);
		add_edge(a,i);
	}
	for(int i = 1, a, b;i <= m;++i) {
		scanf("%d %d",&a,&b);
		add_query(a,b);
		add_query(b,a);
	}
	tarjan(1);
	for(int i = 1;i <= m;++i) 
		printf("%d\n",ans[i]);
}

\(\text{我们先读入所有的询问并对这些询问构建一个邻接表。}\)

\(\text{在遍历到}u\text{时,先}Tarjan\text{遍历完}u\text{的子树,则}u\text{和}u\text{的子树中的节点的最近公共祖先就是}u\text{,并且}\)u\(\text{和}\)u的兄弟节点及其子树\(的最近公共祖先就是u的父亲。\)

\(\text{用一个}color\text{数组,正在访问的节点标记为}1\text{,未访问的标记为}0\text{,已经访问到的即在}\)u的子树中的\(\text{及}\)u的已访问的兄弟节点及其子树中的\(\text{标记为}2\text{。}\)

\(\text{再维护一个并查集,访问完节点}u\text{的一个子树时,就把这个子树的根节点的}fa\text{改为}u\text{。}\)

\(\text{访问完}u\text{的所有子树后,考虑所有与}u\text{相关的询问}\operatorname{LCA}(u,v)\text{,那么}\operatorname{LCA}(u,v)\text{就是}v\text{所在并查集的根。}\)

\(\text{这是一个离线算法,时间复杂度为}\operatorname{O}(n\operatorname{α}(n))\text{,约为}\operatorname{O}(n)\text{。}\)

@bikuhiku

posted @ 2022-06-05 20:45  bikuhiku  阅读(6)  评论(0编辑  收藏  举报