详细见:最近公共祖先 - OI Wiki
目录
定义:
性质:
题目:【模板】最近公共祖先(LCA)
题目链接:【模板】最近公共祖先(LCA) - 洛谷
题面:
朴素算法:
树链剖分模板:
#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
#define N 500005
int head[N], to[N << 1], nex[N << 1], cnt = 1;
int sz[N], dep[N], fa[N], son[N], top[N];
void init(){
cnt = 1;
memset(sz, 0, sizeof(sz));
memset(dep, 0, sizeof(dep));
memset(fa, 0, sizeof(fa));
memset(son, 0, sizeof(son));
memset(top, 0, sizeof(top));
memset(head, 0, sizeof(head));
}
void add(int x, int y){
to[cnt] = y;
nex[cnt] = head[x];
head[x] = cnt++;
}
void dfs1(int rt, int f){
dep[rt] = dep[f] + 1;
sz[rt] = 1;
fa[rt] = f;
for(int i = head[rt]; i; i = nex[i]){
if(to[i] == f){
continue;
}
dfs1(to[i], rt);
if(!son[rt] || sz[to[i]] > sz[son[rt]]){
son[rt] = to[i];
}
sz[rt] += sz[to[i]];
}
}
void dfs2(int rt, int t){
top[rt] = t;
if(!son[rt]) return ;
dfs2(son[rt], t);
for(int i = head[rt]; i; i = nex[i]){
if(to[i] == fa[rt] || to[i] == son[rt]){
continue;
}
dfs2(to[i], to[i]);
}
}
int solve(int x, int y){
while(top[x] != top[y]){
if(dep[top[x]] < dep[top[y]]){
swap(x, y);
}
x = fa[top[x]];
}
return dep[x] < dep[y] ? x : y;
}
int main(){
ios::sync_with_stdio(false);
int n, m, s;
while(cin >> n >> m >> s){
init();
int x, y;
for(int i = 0; i < n - 1; i++){
cin >> x >> y;
add(x, y);
add(y, x);
}
dfs1(s, 0);
dfs2(s, s);
for(int i = 0; i < m; i++){
cin >> x >> y;
cout << solve(x, y) << endl;
}
}
return 0;
}
倍增模板:
#include<bits/stdc++.h>
using namespace std;
#define N 500005
#define endl "\n"
int head[N], to[N<<1], nex[N<<1], cnt=1;
int fa[N][21], dep[N];
void add(int x, int y){
to[cnt] = y;
nex[cnt] = head[x];
head[x] = cnt++;
}
void dfs(int rt, int f){
dep[rt] = dep[f] + 1;
fa[rt][0] = f;
for(int i = 1; (1 << i) <= dep[rt]; i++){ //进制由小到大递推
fa[rt][i] = fa[fa[rt][i - 1]][i - 1];
}
for(int i = head[rt]; i; i = nex[i]){
if(to[i] == f){
continue;
}
dfs(to[i], rt);
}
}
int LCA(int x, int y){
if(dep[x] < dep[y]){
swap(x, y);
}
for(int i = 20; i >= 0; i--){ //进制由大到小开始组合
if(dep[fa[x][i]] >= dep[y]){
x = fa[x][i];
}
}
if(x == y){ //注意特判
return x;
}
for(int i = 20; i >= 0; i--){ //进制从小到大开始组合
if(fa[x][i] != fa[y][i]){
x = fa[x][i];
y = fa[y][i];
}
}
return fa[x][0]; //这一步尤其考虑,为什么x,y不是LCA,二期父节点一定是LCA
}
int main(){
ios::sync_with_stdio(false);
int n, m, s;
while(cin >> n >> m >> s){
memset(head, 0, sizeof(head));
cnt = 1;
int x, y;
for(int i = 0; i < n - 1; i++){
cin >> x >> y;
add(x, y);
add(y, x);
}
dfs(s, 0);
for(int i = 0; i < m; i++){
cin >> x >> y;
cout << LCA(x, y) << endl;
}
}
return 0;
}
tarjan离线求解模板:
思想:
本质就是利用dfs的节点顺序,让我们正在递归两个节点的最近公共祖先时,显然这两个点是属于其子树的节点,那么我们第一次遍历完两个需要求解的两个点时,其最近的尚未被完全遍历完子节点的节点就是他们两个的最近公共祖先
#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
#define N 500005
int head[N], to[N<<1], nex[N<<1], cnt ;
int visit[N], fa[N];
int qhead[N<<1], qnex[N<<1], qcnt = 1, qid[N<<1], ans[N], qto[N<<1];
void add_edge(int x, int y){
to[cnt] = y;
nex[cnt] = head[x];
head[x] = cnt++;
}
void add_query(int x, int y, int w){
qto[qcnt] = y;
qnex[qcnt] = qhead[x];
qid[qcnt] = w;
qhead[x] = qcnt++;
}
int findx(int x){
if(fa[x] == x){
return x;
}
return fa[x] = findx(fa[x]);
}
void tarjan(int rt, int f){
for(int i = head[rt]; i; i = nex[i]){
if(to[i] == f)continue;
tarjan(to[i], rt);
fa[to[i]] = rt;
}
visit[rt] = 1;
for(int i = qhead[rt]; i; i = qnex[i]){
if(visit[qto[i]] == 0)continue;
ans[qid[i]] = findx(qto[i]);
}
}
int main(){
ios::sync_with_stdio(false);
int n, m, s;
while(cin >> n >> m >> s){
int x, y;
memset(head, 0, sizeof(head));
cnt = 1, qcnt = 1;
memset(qhead, 0, sizeof(qhead));
for(int i = 1; i < n; i++){
cin >> x >> y;
add_edge(x, y);
add_edge(y, x);
fa[i] = i;
}
fa[n] = n;
for(int i = 1; i <= m; i++){
cin >> x >> y;
add_query(x, y, i);
add_query(y, x, i);
}
tarjan(s, 0);
for(int i = 1; i <= m; i++){
cout << ans[i] << endl;
}
}
return 0;
}
ST表+RMQ在线求解模板:
思想:
利用dfs的遍历,在遍历两个点的时候,一定会在中间返回到其最近公共祖先,这个时候的公共祖先也就是这两个点的最小值
#include<bits/stdc++.h>
using namespace std;
#define N 500005
#define endl "\n"
int head[N], to[N<<1], nex[N<<1],cnt = 1;
int id[N], tot, last;
int st[N<<2][30];
void add(int x, int y){
to[cnt] = y;
nex[cnt] = head[x];
head[x] = cnt++;
}
void dfs(int rt, int f){
id[rt] = last = ++tot;
st[tot][0] = rt;
for(int i = head[rt]; i; i = nex[i]){
if(to[i] == f)continue;
dfs(to[i], rt);
st[++tot][0] = rt;
}
}
int Min(int a, int b){
return id[a] < id[b] ? a : b;
}
int main(){
ios::sync_with_stdio(false);
int n, m, s;
while(cin >> n >> m >> s){
int x, y;
cnt = 1;
tot = 0;
last = 0;
memset(head, 0, sizeof(head));
for(int i = 1; i < n; i++){
cin >> x >> y;
add(x, y);
add(y, x);
}
dfs(s, 0);
int k = log(last) / log(2);
for(int j = 1; j <= k; j++){
for(int i = 1; i + (1 << j) - 1 <= last; i++){
st[i][j] = Min(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
}
}
for(int i = 1; i <= m; i++){
cin >> x >> y;
x = id[x];
y = id[y];
if(x > y){
swap(x, y);
}int k = log(y - x + 1) / log(2);
cout << Min(st[x][k], st[y - (1 << k) + 1][k]) << endl;
}
}
return 0;
}