鏈接: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找到第二個點,記錄路徑,兩條路徑的第一個重合點也是公共祖先。