【模板】最近公共祖先【LCA】
##题目大意:
题目链接:https://www.luogu.org/problemnew/show/P3379
给出一个树和组询问,对于每个询问输出两个结点的。
##思路:
思路一:树上倍增
树上倍增是的基本方法之一。其做法是先将和跳到统一深度上,再利用倍增思想找到两点的。
所以要先用求出每个点的深度以及它的祖宗,然后在对于每一询问完成上述操作即可。
思路二:表
对于一棵树上的两个不同节点和,从根开始跑一遍欧拉序,设在欧拉序中第一次出现的位置为,第一出现的位置是,那么和的就是欧拉序中到之间深度最低的(即)节点就是它们的。
证明略。但是很明显是正确的。
所以,先跑一边求出和欧拉序,再利用表求出任意区间的最小值(),然后再读入输出即可。
注意!此方法时间较慢,需要输入输出流才能过!
##代码:
树上倍增:
#include <cstdio>
#include <iostream>
#include <cmath>
#include <algorithm>
#define N 500030
#define LG 25
using namespace std;
int dep[N+1],head[N+1],lg[LG+1],k,n,m,s,x,y,f[N+1][LG+1];
struct edge //邻接表
{
int to,next;
}e[N<<1+1];
void add(int from,int to)
{
e[++k].to=to;
e[k].next=head[from];
head[from]=k;
}
void dfs(int x,int fa) //求每个点的深度
{
dep[x]=dep[fa]+1;
f[x][0]=fa;
for (int i=1;(1<<i)<=dep[x];i++)
f[x][i]=f[f[x][i-1]][i-1]; //树上倍增祖宗
for (int i=head[x];i;i=e[i].next)
if (e[i].to!=fa)
dfs(e[i].to,x);
}
int lca(int x,int y) //倍增
{
if (dep[y]>dep[x]) swap(x,y);
for (int i=LG;i>=0;i--)
if (dep[f[x][i]]>=dep[y]) x=f[x][i]; //到达同一高度
if (x==y) return x;
for (int i=LG;i>=0;i--) //倍增
if (f[x][i]!=f[y][i])
{
x=f[x][i];
y=f[y][i];
}
return f[x][0];
}
int main()
{
//freopen("lca.in","r",stdin);
scanf("%d%d%d",&n,&m,&s);
for (int i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
lg[1]=0;lg[2]=1;
for (int i=3;i<=LG;i++)
lg[i]=lg[i>>1]+1; //预处理
dfs(s,0);
for (int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
printf("%d\n",lca(x,y));
}
return 0;
}
表:
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define N 500020
#define LG 20
using namespace std;
int first[N<<1+1],dep[N<<1+1],vis[N<<1+1],rmq[N<<1+1][LG],num[N<<1+1][LG],head[N<<1/+1];
int n,m,s,k,sum,x,y,z;
struct edge
{
int next,to;
}e[N<<1+1];
int f;
char c;
int read() //输入流
{
f=0;
while(c=getchar(),c<=47||c>=58);f=(f<<3)+(f<<1)+c-48;
while(c=getchar(),c>=48&&c<=57) f=(f<<3)+(f<<1)+c-48;
return f;
}
void write(int x) //输出流(可以不用)
{
if(x>9) write(x/10);else putchar(x%10+48);
return;
}
void add(int from,int to)
{
k++;
e[k].to=to;
e[k].next=head[from];
head[from]=k;
}
void dfs(int x,int k) //求欧拉序
{
sum++;
first[x]=sum;
dep[sum]=k;
vis[sum]=x;
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
if (first[v]) continue; //这个点已经到达过
dfs(v,k+1);
sum++;
dep[sum]=k;
vis[sum]=x;
}
return;
}
int main()
{
//freopen("q.txt","r",stdin);
memset(head,-1,sizeof(head));
//scanf("%d%d%d",&n,&m,&s);
n=read();
m=read();
s=read();
for (int i=1;i<n;i++)
{
//scanf("%d%d",&x,&y);
x=read();
y=read();
add(x,y);
add(y,x);
}
dfs(s,1);
for (int i=1;i<=sum;i++) //预处理
{
rmq[i][0]=dep[i];
num[i][0]=vis[i];
}
for (int j=1;j<=log2(sum);j++)
for (int i=1;i+(1<<j)-1<=sum;i++) //ST表,RMQ
if (rmq[i][j-1]<rmq[i+(1<<(j-1))][j-1])
{
rmq[i][j]=rmq[i][j-1];
num[i][j]=num[i][j-1];
}
else
{
rmq[i][j]=rmq[i+(1<<(j-1))][j-1];
num[i][j]=num[i+(1<<(j-1))][j-1];
}
for (int i=1;i<=m;i++)
{
//scanf("%d%d",&x,&y);
x=read();
y=read();
if (first[x]>first[y]) swap(x,y);
x=first[x];
y=first[y];
z=log2(y-x+1); //求log
if (rmq[x][z]>rmq[y-(1<<z)+1][z])
printf("%d\n",num[y-(1<<z)+1][z]);
else printf("%d\n",num[x][z]);
//putchar(10);
}
getchar();getchar();
return 0;
}