链接: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找到第二个点,记录路径,两条路径的第一个重合点也是公共祖先。
POJ 1330 Nearest Common Ancestors LCA(在线RMQ,离线Tarjan)
时间: 2024-10-03 06:55:10