链接:http://poj.org/problem?id=1330
题意:只看题目就知道题目是什么意思了,最近公共祖先,求在一棵树上两个节点的最近公共祖先。
思路:求最近公共祖先有两种算法,在线和离线,在线方法是用RMQ求LCA,一句话总结就是在从DFS时,从第一个点到第二个点的最短路径中深度最浅的点就是公共祖先,用RMQ处理,一般问题的最优解决方式的复杂度是O(NlogN)的预处理+N*O(1)的查询。离线方法是Tarjan算法,将所有询问的两个点都记录下来,在DFS过程中不断将每个点自身作为祖先,然后将所有子树遍历结束后,祖先变为它的父节点,在过程中如果遍历到某个节点,同时关于它的询问的另一点已遍历,则此时另一点的祖先就是它们的公共祖先,虽然算法不是很好说明白,不过看代码还是很显而易见的。
代码:
在线算法:RMQ求LCA
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <ctype.h>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
#define eps 1e-8
#define INF 0x7fffffff
#define maxn 10005
#define PI acos(-1.0)
#define seed 31//131,1313
typedef long long LL;
typedef unsigned long long ULL;
using namespace std;
int stTable [maxn*2][32];
int preLog2[maxn*2];
int depth=0;
int d[maxn*2];
bool vis[maxn];
int bn=0,b[maxn*2]; //深度序列
int f[maxn*2]; //对应深度序列中的结点编号
int p[maxn]; //结点在深度序列中的首位置
int dis[maxn]; //结点到根的距离
int head[maxn];
void st_prepare(int n,int *array)
{
preLog2[1]=0;
for(int i=2;i<=n;i++)
{
preLog2[i]=preLog2[i-1];
if((1<<preLog2[i]+1)==i)
preLog2[i]++;
}
for(int i=n-1;i>=0;i--)
{
stTable[i][0]=array[i];
for(int j=1;(i+(1<<j)-1)<n;j++)
{
stTable[i][j]=min(stTable[i][j-1],stTable[i+(1<<j-1)][j-1]);
}
}
return ;
}
int query_min(int l,int r)
{
int len=r-l+1,k=preLog2[len];
return min(stTable[l][k],stTable[r-(1<<k)+1][k]);
}
int point[maxn]; //记录每个点对应的第一条边的序号
struct Edge
{
int v;//连接点
int next;//下一条从此边的出发点发出的边
}edge[maxn*2];
int top;
int init()
{
memset(vis,0,sizeof(vis));
memset(point,-1,sizeof(point));
memset(dis,0,sizeof(dis));
top=0;
bn=0;
depth=0;
}
int add_edge(int u,int v)
{
edge[top].v=v;
edge[top].next=point[u];//上一条边的编号
point[u]=top++;//u点的第一条边编号变成head
}
void dfs(int u,int fa)
{
int tmp=++depth;
b[++bn]=tmp; f[tmp]=u; p[u]=bn;
for (int i=point[u]; i!=-1; i=edge[i].next)
{
int v=edge[i].v;
if (v==fa) continue;
dis[v]=dis[u]+1;//edge[i].v
dfs(v,u);
b[++bn]=tmp;
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
init();
int tot,root=0,aa,bb;
scanf("%d",&tot);
for(int i=1;i<=tot;i++)
head[i]=i;
for(int i=1;i<=tot-1;i++)
{
scanf("%d%d",&aa,&bb);
add_edge(aa,bb);
add_edge(bb,aa);
head[bb]=aa;
}
for(int i=1;i<=tot;i++)
{
if(head[i]==i)
{
root=i;
break;
}
}
dfs(root,root);
st_prepare(tot*2-1,b);
scanf("%d%d",&aa,&bb);
if(p[aa]<p[bb])
cout<<f[query_min(p[aa],p[bb])]<<endl;
else cout<<f[query_min(p[bb],p[aa])]<<endl;
}
}
离线算法:Tarjan算法
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <ctype.h>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <stack>
#include <string>
#include <vector>
#define eps 1e-8
#define INF 0x7fffffff
#define maxn 10005
#define PI acos(-1.0)
#define seed 31//131,1313
typedef long long LL;
typedef unsigned long long ULL;
using namespace std;
int pre[maxn],point[maxn],point2[maxn];
bool vis[maxn];
struct Edge
{
int v;//连接点
int next;//下一条从此边的出发点发出的边
} edge[maxn*2];
struct Query
{
int v;
int w;
int next;
} query[maxn];
int top,top2;
int init()
{
memset(vis,0,sizeof(vis));
memset(point,-1,sizeof(point));
memset(point2,-1,sizeof(point2));
top=0;
top2=0;
}
int add_edge(int u,int v)
{
edge[top].v=v;
edge[top].next=point[u];//上一条边的编号
point[u]=top++;//u点的第一条边编号变成head
}
int findset(int x) //并查集
{
if(x!=pre[x])
{
pre[x]=findset(pre[x]); //路径压缩
}
return pre[x];
}
int add_query(int u,int v)
{
query[top2].v=v;
query[top2].w=-1;
query[top2].next=point2[u];//上一条边的编号
point2[u]=top2++;//u点的第一条边编号变成head
query[top2].v=u;
query[top2].w=-1;
query[top2].next=point2[v];//上一条边的编号
point2[v]=top2++;//u点的第一条边编号变成head
}
int lca(int u,int f) //当前节点,父节点
{
pre[u]=u; //设立当前节点的集合
for(int i=point[u]; i!=-1; i=edge[i].next)
{
if(edge[i].v==f)
continue;
lca(edge[i].v,u); //搜索子树
pre[edge[i].v]=u; //合并子树
}
vis[u]=1; //以u点为集合的点搜索完毕
for(int i=point2[u]; i!=-1; i=query[i].next)
{
if(vis[query[i].v]==1)
query[i].w=findset(query[i].v);
}
return 0;
}
int main()
{
int root[maxn];
int T;
scanf("%d",&T);
for(int ii=0; ii<T; ii++)
{
init();
int tot,r=-1,a,b;
scanf("%d",&tot);
for(int i=1; i<=tot; i++)
root[i]=i;
for(int i=0; i<tot-1; i++)
{
scanf("%d%d",&a,&b);
add_edge(a,b);
add_edge(b,a);
root[b]=a;
}
for(int i=1; i<=tot; i++)
if(root[i]==i)
r=i;//树的根
scanf("%d%d",&a,&b);
add_query(a,b);
lca(r,r);
//cout<<top2<<endl;
for(int i=0;i<top2;i++)
{
if(query[i].w!=-1)
printf("%d\n",query[i].w);
}
}
return 0;
}
P.S.其实这道题只有一次询问,所以先DFS第一个点,记录路径,然后DFS找到第二个点,记录路径,两条路径的第一个重合点也是公共祖先。