POJ 1330 Nearest Common Ancestors(LCA)
tags : acm
树链剖分搞不来,写道LCA压压惊.由于Tarjin没怎么看懂,所以就直接用dfs+st搞
题意:
给定一颗树,求两个节点的最近公共祖先
解析:
标准的LCA问题,关于LCA的做法可以参考这里
代码:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
using namespace std;
#define MAXN 12000
int T,N;
vector<int> cnn[MAXN];
int pos[MAXN]; //pos数组在确定根节点时用于记录入度,在dfs时用于记录节点在路径中第一次出现的位置
int d[MAXN*2][100]; //st算法中需要用到的数组
vector<int> path; //用于记录路径
vector<int> depth; //路径对应的深度
void dfs(int u,int d)
{
int sz=cnn[u].size();
for (int i=0;i<sz;i++)
{
int v=cnn[u][i];
if (pos[v]==-1) //找到连接未访问过的点的边
{
//记录这条边远离根的端点
pos[v]=path.size();
path.push_back(v);
depth.push_back(d);
//访问下一层
dfs(v,d+1);
//记录这条边靠近根的端点
path.push_back(u);
depth.push_back(d);
}
}
}
void RMQ_init(const vector<int> A)
{
int n=A.size();
for (int i=0;i<n;i++) d[i][0]=A[i];
for (int j=1;(1<<j)<=n;j++)
for (int i=0;i+(1<<j)-1<n;i++)
d[i][j] = min(d[i][j-1],d[i+(1<<(j-1))][j-1]);
}
int RMQ(int L,int R)
{
int k=0;
while ((1<<k+1) <= R-L+1) k++;
return min(d[L][k],d[R-(1<<k)+1][k]);
}
int main()
{
scanf("%d",&T);
while (T--)
{
memset(pos,0,sizeof (pos));
int u,v;
scanf("%d",&N);
for (int i=0;i<=N;i++)
cnn[i].clear();
for (int i=0;i<N-1;i++)
{
scanf("%d%d",&u,&v);
cnn[u].push_back(v);
cnn[v].push_back(u);
pos[v]++;//记录入度
}
scanf("%d%d",&u,&v);
//需要先确定根节点的标号,入度为0的点即为根节点
int root=0;
for (int i=1;i<=N;i++)
if (!pos[i])
{
root = i;
break;
}
//初始化标记数组,路径数组以及路径对应的深度数组
memset(pos,-1,sizeof (pos));
path.clear();
depth.clear();
//标记根节点
pos[root]=path.size();
path.push_back(root);
depth.push_back(1);
//进行dfs遍历,记录任意两节点之间的一条可行路径,这条路径经过该两点的最近公共祖先,且该祖先在这条路径中深度最小
dfs(root,1);
//初始化
RMQ_init(depth);
if (pos[u]>pos[v])
{
int tmp=u;
u=v;
v=tmp;
}
//获得LCA对应的深度
int min_d=RMQ(pos[u],pos[v]);
//在[pos[u],pos[v]]范围内找到深度为min_d的点(这样的点只有一个),即为LCA
for (int i=pos[u];i<=pos[v];i++)
{
if (depth[i]==min_d)
{
printf("%d\n",path[i]);
break;
}
}
}
return 0;
}