裸的求最近公共祖先问题,其中poj另一个同类型的题目输入操作相当变态无聊,就拿这道题备用模板吧。
思路:先是一遍从根节点的深度优先搜索,记录些有用的信息(感觉描述起来好麻烦,不展开了,看了以下核心思想,再画画图就能看出来)。然后对信息进行一遍RMQ预处理(Nlog(N)的复杂度)。然后剩下的就是O(1)复杂度的访问随意两点的公共祖先了。
先定义一个访问顺序的概念:每个节点的访问顺序是指第一次访问到这个节点时候的排名(再翻译一下:a节点的访问顺序就是:在所有的第一次访问到每个节点的排名中,a节点是第几个先被访问到的(累死了,我的语文真得好好再学学了));
再核心部分:要查的两个节点a,b的LCA在深度优先搜索时一定会出现在第一次访问到a和第一次访问到b的时间之间(注意:深搜回溯到某节点时也算访问一次此节点,一节点可以被访问多次)。而且在这之间a,b的LCA的访问顺序肯定是最小的。所以只需要在RMQ预处理以后,找到第一次访问到a和第一次访问到b的时间之间的最小访问顺序的节点,那就是他们的LCA了。
代码:
#include <iostream>
#include <vector>
#include <stdio.h>
#include <cstring>
#include <cmath>
using namespace std;
vector<int> vec[30910];
int rem[39100];
int tool[39010];
int firsttouch[30910];
int firstposition[30910];
int number[50000];
int rmq[50100][25];
int change[30910];
int goal1,goal2;
int tree;
int ans;
int help=0;
int touch=1;
int n;
int pow(int k)
{
int an=1;
int x=2;
while(k>0)
{
if(k&1)
an*=x;
x*=x;
k/=2;
}
return an;
}
void dfs(int k)
{
// if(help>20000)
// return ;
firsttouch[k]=touch;
change[touch]=k;
touch++;
number[++help]=k;
firstposition[k]=help;
for(int i=0;i<vec[k].size();i++)
{
dfs(vec[k][i]);
number[++help]=k;
}
}
void pretreat()
{
for(int j=1;j<=help;j++)
rmq[j][0]=firsttouch[number[j]];
for(int i=1;pow(i)<=help;i++)
{
for(int j=1;j<=help;j++)
{
if(j+pow(i-1)>help)
{
rmq[j][i]=rmq[j][i-1];
}
else
{
rmq[j][i]=min(rmq[j][i-1],rmq[j+pow(i-1)][i-1]);
}
}
}
}
int main()
{
int N;
scanf("%d",&N);
for(int u=0;u<N;u++)
{
scanf("%d",&n);
help=0;
touch=0;
for(int i=0;i<10000;i++)
{
rem[i]=0;
tool[i]=0;
vec[i].clear();
}
for(int i=1;i<n;i++)
{
int a,b;
scanf("%d%d",&a,&b);
vec[a].push_back(b);
tool[b]++;
}
for(int i=1;i<=n;i++)
if(tool[i]==0)
{
tree=i;
break;
}
dfs(tree);
pretreat();
scanf("%d%d",&goal1,&goal2);
if(goal1==goal2)
{
rem[goal1]++;
continue;
}
int a=min(firstposition[goal1],firstposition[goal2]);
int b=max(firstposition[goal1],firstposition[goal2]);
int h=int(log(double(b-a)*1.0)/log(2.0));
cout<<change[min(rmq[a][h],rmq[b-pow(h)+1][h])]<<endl;;
}
return 0;
}