最近公共祖先
LCA问题
解决方法:
1. 向上标记法 O(n)
从x向上走到根节点, 并标记路径上经过的点
从y向上走到根节点, 当遇到第一个被标记的点就找到了LCA(x, y)
2. 倍增法 O(mlogn)
- 预处理
fa[i,j]
表示从i
开始,向上走2^j
步所能走到的节点。0<=j<=logn
- depth[i] 表示深度
- 查询 O(logn) 预处理O(nlogn)
- 哨兵: 如果从i开始跳2^j步会跳过根节点那么
fa[i,j]=0,depth[0]=0
步骤:
1.先将两个点跳到同一层(二进制拼凑思想)O(logn)
depth[x] -depth[y]
if(depth[f(x,k)]>=depth[y])
2. 让两个点同时往上跳,一直跳到他们的最近公共祖先的下一层 O(logn)
if(f(a,k)==f(b,k)) 是一个公共祖先,但不一定是最近公共祖先
else if(f(a,k)!=f(b,k)) 一定还没有走到公共祖先上
fa[6,0] = 4 fa[6,1] = 2 fa[6,2]=NULL
if(!j) f(i,j)=i的父节点
else f(i,j)=f(f(i,j-1),j-1)
在线算法:每读入一个询问就输出
离线算法:把所有询问读入,最后统一输出
方法3:Tarjan算法——离线求LCA O(n+m)
线性
向上标记法的优化
基于深搜 将节点分为三类
1.已经遍历过且回溯过的点 标记为1
2.正在搜索的分支 标记为2
3.还未搜索到的点 标记为0
例题1:AcWing1172.祖孙查询(倍增法)
答案
#include <iostream>
#include <algorithm>
#include<bits/stdc++.h>
#define ll long long
const int N = 4e4 + 10;
const int M = 2*N;
using namespace std;
int n,m;
int h[N],e[M],ne[M],idx;
int depth[N],fa[N][16];
int q[N];
void add(int a,int b){
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void BFS(int root){
memset(depth,0x3f,sizeof depth);
depth[0]=0,depth[root]=1;
int hh=0,tt=0;
q[0]=root;
while(hh<=tt){
int t=q[hh++];
for(int i=h[t];~i;i=ne[i]){
int j=e[i];
if(depth[j]>depth[t]+1){
depth[j]=depth[t]+1;
q[++tt]=j;
fa[j][0]=t;
for(int k=1;k<=15;k++) fa[j][k]=fa[fa[j][k-1]][k-1];
}
}
}
}
int LCA(int a,int b){
if(depth[a]<depth[b]) swap(a,b);
for(int k=15;k>=0;k--){
if(depth[fa[a][k]]>=depth[b]) a=fa[a][k];
}
if(a==b) return a;
for(int k=15;k>=0;k--){
if(fa[a][k]!=fa[b][k]){
a=fa[a][k];
b=fa[b][k];
}
}
return fa[a][0];
}
inline void solve(){
cin>>n;
int root=0;
memset(h,-1,sizeof h);
for(int i=0;i<n;i++){
int a,b;
cin>>a>>b;
if(b==-1) root=a;
else add(a,b),add(b,a);
}
BFS(root);
cin>>m;
while(m--){
int x,y;
cin>>x>>y;
int p=LCA(x,y);
if(p==x) cout<<"1"<<endl;
else if(p==y) cout<<"2"<<endl;
else cout<<"0"<<endl;
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t=1;
//cin>>t;
while(t--) solve();
return 0;
}
例题2:AcWing1171.距离(Tarjan离线LCA算法)
答案
#include <iostream>
#include <algorithm>
#include<bits/stdc++.h>
#define ll long long
#define PII pair<int,int>
#define eb emplace_back
const int N = 2e4 + 10;
const int M = 2*N;
using namespace std;
int n,m;
int h[N],e[M],w[M],ne[M],idx;
int dist[N];
int p[N];
int res[N];
int st[N];
vector<PII>que[N]; // first存查询的另外一个点、second存查询编号
void add(int a,int b,int c){
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
void DFS(int u,int fa){
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa) continue;
dist[j]=dist[u]+w[i];
DFS(j,u);
}
}
int Find(int x){
if(p[x]!=x) p[x]=Find(p[x]);
return p[x];
}
void tarjan(int u){
st[u]=1;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(!st[j]){
tarjan(j);
p[j]=u;
}
}
for(auto it:que[u]){
int y=it.first;
int id=it.second;
if(st[y]==2){
int anc=Find(y);
res[id]=dist[u]+dist[y]-dist[anc]*2;
}
}
st[u]=2;
}
inline void solve(){
cin>>n>>m;
memset(h,-1,sizeof h);
for(int i=0;i<n-1;i++){
int a,b,c;
cin>>a>>b>>c;
add(a,b,c);
add(b,a,c);
}
for(int i=0;i<m;i++){
int a,b;
cin>>a>>b;
if(a!=b){
que[a].eb(PII{b,i});
que[b].eb(PII{a,i});
}
}
for(int i=1;i<=n;i++) p[i]=i;
DFS(1,-1);
tarjan(1);
for(int i=0;i<m;i++) cout<<res[i]<<endl;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t=1;
//cin>>t;
while(t--) solve();
return 0;
}