描述:
给定一棵树,请查询结点u和v的最近公共祖先。最近公共祖先,就是两个节点在这棵树上深度最大(离根结点最远)的公共的祖先节点,结点的祖先也可以是自身。
输入:
输入数据第一行为结点个数n(n<=900),接下来有n行,每行格式如下:
x m y1, y2, ... ym
表示结点x有m个孩子y1, y2, ... ym
接下来有一个正整数q(q<=100000),表示查询次数,之后有q行,每行两个正整数u和v,表示待查寻的结点编号。
树结点的编号为1~n。
输出:
对每次查询,输出u和v的最近公共祖先,格式为:
u v = w
其中w表示u和v的最近公共祖先结点编号。
样例输入:
5
4 0
5 3 1 4 2
1 0
2 1 3
3 0
6
1 5
1 4
4 2
2 3
1 3
4 3
样例输出:
1 5 = 5
1 4 = 5
4 2 = 5
2 3 = 2
1 3 = 5
4 3 = 5
提示:
样例中的树为:
题目分析:这是一道求lca的板子题,由于数据量不小,所以用到倍增lca算法(详情解释见代码)
代码示例:
#include<bits/stdc++.h>
using namespace std;
const int N=9e2+2;
struct node{ //经典链式前向星一键三连(结构体,head数组,add函数)
int to,next;
}q[N*N]; //题目没说具体有多少条边,默默开一个最逆天(bushi)情况的大小
int deep[N],fat[N][11],log2n,n,cnt=0,head[N],r[N]={0},s;
//deep表示每个结点的深度;fat用来记录每个结点祖先;r表示每个节点的入度,用于找出根节点
void add(int u,int v)
{
q[cnt].to=v;
q[cnt].next=head[u];
head[u]=cnt++;
}
void dfs(int now,int father) //dfs打表deep数组和fat数组
{ //now为当前节点,father为其父亲节点
deep[now]=deep[father]+1; //当前节点的深度为其父亲节点的深度+1
fat[now][0]=father;
for(int i=1;(1<<i)<=deep[now];i++) //1<<i即2^i
fat[now][i]=fat[fat[now][i-1]][i-1]; //算法核心:2^i==2^(i-1)+2^(i-1)
for(int i=head[now];i!=-1;i=q[i].next)
if(q[i].to!=father)
dfs(q[i].to,now);
}
int lca(int u,int v)
{
int du=deep[u],dv=deep[v];
if(du!=dv)
{
if(du<dv) //保证u的深度>=v的深度
{
swap(u,v);
swap(du,dv);
}
int d=du-dv;
for(int i=0;i<=log2n;i++)
if((1<<i)&d) //若d>=2^i
u=fat[u][i];
}
if(u==v)
return u;
for(int i=log2n;i>=0;i--)
if(deep[fat[u][i]]>0&&fat[u][i]!=fat[v][i])
{ //若u,v的父节点不相同,就各自跳到其父节点上
u=fat[u][i],v=fat[v][i];
}
//循环结束,跳到公共祖先的下一深度,返回公共祖先即可
return fat[u][0];
}
int main()
{
int m;
scanf("%d",&n);
log2n=log(n)/log(2)+0.5; //根据n求整棵树的最大深度
memset(head,-1,sizeof(head));
for(int i=1;i<=n;i++)
{
int x;
scanf("%d%d",&x,&m);
while(m--)
{
int y;
scanf("%d",&y);
add(x,y);
r[y]++;
}
}
for(int i=1;i<=n;i++)
if(!r[i])
{
s=i;break; //找到根节点
}
deep[0]=0;
dfs(s,0);
scanf("%d",&m);
while(m--)
{
int u,v;
scanf("%d%d",&u,&v);
printf("%d %d = %d\n",u,v,lca(u,v));
}
return 0;
}