题目:
模板题不用概括大意了吧
给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
解题思路:
树上倍增
fa[i,j]表示第i个点的2
j
j
级别的祖先
depth[x]表示x的深度
我们预先算出log_2(i)+1的值,这样每次调用就是O(1),如果用C语言的log会比较慢,自己推可以达到常数优化的效果
lg[i]=log_2(i)+1=lg[i-1]+(1<<lg[i-1]==i)
接着我们把比较深的点往上拉,拉到与另外一个点的高度相同再开始倍增。
倍增就是每次往上跳2的次幂个节点,我们不是1,2,4,8……这样跳,这样跳过了又要回溯一遍,如果我们64,32,16……这样跳就不用回溯
我们跳完后相同的并不是这两个点,而是它们的父亲,于是返回值是fa[x][0] or fa[y][0]
Accepted code:
#include<bits/stdc++.h>
#define N 500001
using namespace std;
struct node{
int y,next;
}a[N<<1];
int last[N],depth[N],fa[N][25],lg[N],cnt;
int n,m,s;
inline void add(int x,int y)//建树
{
a[++cnt].y=y;a[cnt].next=last[x];last[x]=cnt;
a[++cnt].y=x;a[cnt].next=last[y];last[y]=cnt;
}
inline int read()
{
int f=0,ag=1;char c=getchar();
while(!isdigit(c)) {if (c=='-') ag=-1; c=getchar();}
while(isdigit(c)){f=(f<<3)+(f<<1)+c-48;c=getchar();}
return f*ag;
}
inline void CSH()
{
for(int i=1;i<=n;i++)
lg[i]=lg[i-1]+(1<<lg[i-1]==i);
}
void dfs(int now,int f)//now是当前点,f是上次搜的点
{
depth[now]=depth[f]+1;//记录深度
fa[now][0]=f;//记录父亲
for(int i=1;(1<<i)<=depth[now];i++)
fa[now][i]=fa[fa[now][i-1]][i-1];
//就是爷爷是父亲的父亲,高祖父是爷爷的爷爷这个道理
for (int i=last[now];i;i=a[i].next)
if (a[i].y!=f)
dfs(a[i].y,now);//往下搜
}
int LCA(int x,int y)
{
if (depth[x]<depth[y])
swap(x,y);//不妨设x的深度≥y的深度
while(depth[x]>depth[y])
x=fa[x][lg[depth[x]-depth[y]]-1];
//把x拉到y的深度
if (x==y) return x;//如果y是x的祖先,则返回x(y)
for (int i=lg[depth[x]];i>=0;i--)
if (fa[x][i]!=fa[y][i])
x=fa[x][i],y=fa[y][i];//往上跳
return fa[x][0];//返回x(y)的父亲
}
int main()
{
n=read();m=read();s=read();//输入
for (int i=1;i<n;i++)
{
int x=read(),y=read();add(x,y);//邻接表
}
dfs(s,0);//计算每个点的深度以及它们的2^j(0..n)级祖先
CSH();//常数优化
for (int i=1;i<=m;i++)
{
int x=read(),y=read();
printf("%d\n",LCA(x,y));
}
}