我们知道,最近公共祖先是指有根树上找出任意两个节点,u,v的最近的公共祖先。
这是洛谷的模板题:
最近公共祖先LCA
解释都在代码里:
#pragma GCC optimize(2)
#include
using namespace std;
#define IOS ios::sync_with_stdio(0)
#define ull unsigned ll
#define uint unsigned
#define pai pair
#define pal pair
#define IT iterator
#define pb push_back
#define fi first
#define se second
#define For(i,j,k) for (int i=(int)(j);i<=(int)(k);++i)
#define Rep(i,j,k) for (int i=(int)(j);i>=(int)(k);--i)
#define endl '\n'
#define ll long long
const int N=500010;
int head[N],tot;
struct node
{
int to,nxt;
}tree[N<<1];//定义节点
void add(int x,int y)
{
tree[++tot].to=y;
tree[tot].nxt=head[x];
head[x]=tot;
}//链式前向星存图
int depth[N];//depth数组用来存每一个节点的深度;
int fa[N][20];//fa[i][j]表示节点i的2^j级祖先;
int lg[N];//倍增遍历的lg数组;
void dfs(int now,int fath)//now表示当前节点,fath表示它的直接父亲节点
{
fa[now][0]=fath;//当前节点的父亲;
depth[now]=depth[fath]+1;//当前节点的深度就是父亲节点的深度+1;
for(int i=1;i<lg[depth[now]];++i)
{
fa[now][i]=fa[fa[now][i-1]][i-1];//实际上是一个小递归,就是一个节点的2^i级祖先为它的2^i级祖先的2^i级祖先;
}
for(int i=head[now];i;i=tree[i].nxt)
{
if(tree[i].to!=fath)
dfs(tree[i].to,now);
}
}//以上dfs实际上是一种预处理,找出每一个节点的2^i级祖先。
int LCA(int x,int y)
{
if(depth[x]<depth[y])//为了方便处理,我们设x的深度大于y的深度;
swap(x,y);
while(depth[x]>depth[y])
{
x=fa[x][lg[depth[x]-depth[y]]-1];//先让x跳到与y的同一深度;
}
if(x==y)//如果x是y的祖先,那么它们的公共祖先为x;
return x;
for(int k=lg[depth[x]]-1;k>=0;--k)//两个节点一起往上跳,每次的幅度就是lg数组
if(fa[x][k]!=fa[y][k])//因为一直不相等,所以我们会跳到LCA的下一层
x=fa[x][k],y=fa[y][k];
return fa[x][0];//最后返回下一层的父亲,也就是我们要求的LCA;
}
int main()
{
int n,m,s;
scanf("%d%d%d",&n,&m,&s);
for(int i=1;i<=n-1;++i)
{
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
for(int i=1;i<=n;++i)
{
lg[i]=lg[i-1]+(1<<lg[i-1]==i);
}//常数优化;
dfs(s,0);
for(int i=1;i<=m;++i)
{
int x,y;
scanf("%d%d",&x,&y);
printf("%d\n",LCA(x,y));
}
return 0;
}
#pragma GCC optimize(2)
#include
using namespace std;
#define IOS ios::sync_with_stdio(0)
#define ull unsigned ll
#define uint unsigned
#define pai pair
#define pal pair
#define IT iterator
#define pb push_back
#define fi first
#define se second
#define For(i,j,k) for (int i=(int)(j);i<=(int)(k);++i)
#define Rep(i,j,k) for (int i=(int)(j);i>=(int)(k);--i)
#define endl '\n'
#define ll long long
const int maxn = 500005;
const int maxm = 1000005;
int head[maxn],nxt[maxm],to[maxm],cnt;
int id[maxm],vis[maxm],depth[maxm],tot;
int f[maxm][20],lg[maxm];
//定义dp[i][j] 表示下标i开始长度为2^j的区间的极值。如:dp[2][1]表示区间[2,3]的极值
inline int read()
{
int sum = 0,p = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-')
p = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
(sum *= 10)+= ch - '0';
ch = getchar();
}
return sum * p;
}//快读
void add(int x,int y)//链式前向星加边
{
nxt[++cnt] = head[x];
to[cnt] = y;
head[x] = cnt;
return;
}
void dfs(int u,int fa,int dep)//u代表当前节点,fa是当前节点的父亲,dep是当前节点的深度
{
id[u] = ++tot;//id[u]表示在欧拉序中第一次被访问时的下标。
vis[tot] = u;//vis[i]存的是第i个节点在欧拉序中的编号
depth[tot] = dep;//depth[]是存节点的深度
for(int i = head[u];i;i = nxt[i])//链式前向星遍历
{
int v = to[i];
if(v == fa)
continue;
dfs(v,u,dep+1);
vis[++tot] = u;
depth[tot] = dep;
}
return;
}//欧拉序处理每个节点得到id[u],vis[i],depth[i];
void RMQ()
{
for(int i = 1;i <= tot;i++)//tot是跑过欧拉序后的总点数
lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);//常数优化预处理
for(int i = 1;i <= tot;i++)
f[i][0] = i;
for(int j = 1;(1 << j) <= tot;j++)
for(int i = 1;i + (1 << j) - 1 <= tot;i++)
{
int a = f[i][j-1];
int b = f[i + (1 << (j - 1))][j - 1];
if(depth[a] <= depth[b])
f[i][j] = a;
else
f[i][j] = b;
}
return;
}
int LCA(int x,int y)
{
int r = id[x];
int l = id[y];
if(r < l)
swap(r,l);
int k = lg[r - l + 1] - 1;
int a = f[l][k];
int b = f[r - (1 << k) + 1][k];
if(depth[a] <= depth[b])
return vis[a];
else
return vis[b];
}
int main()
{
int n = read(),m = read(),s = read();
int x,y;
for(int i = 1;i < n;i++)
{
x = read(),y = read();
add(x,y);
add(y,x);
}
dfs(s,0,1);
RMQ();
for(int i = 1;i <= m;i++)
{
x = read(),y = read();
printf("%d\n",LCA(x,y));
}
return 0;
}