题目链接:传送门
题目大意:n个点,n-1条边,询问一对点的公共祖先是?
题目思路:模板题。分别用离线算法和在线算法计算。
在线算法:利用DFS+ST算法,利用RMQ的求区间最大最小值的方法计算。
利用DFS计算出的欧拉序列,要求的两点之间最小值就是所求的公共祖先。
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <math.h>
#include <stdlib.h>
#include <time.h>
using namespace std;
/*
* LCA (POJ 1330)
* 在线算法 DFS + ST
*/
const int MAXN = 10010;
int rmq[2*MAXN];//rmq数组,就是欧拉序列对应的深度序列
struct ST
{
int mm[2*MAXN];
int dp[2*MAXN][20];//最小值对应的下标
void init(int n)
{
mm[0] = -1;
for(int i = 1;i <= n;i++)
{
mm[i] = ((i&(i-1)) == 0)?mm[i-1]+1:mm[i-1];
dp[i][0] = i;
}
for(int j = 1; j <= mm[n];j++)
for(int i = 1; i + (1<<j) - 1 <= n; i++)
dp[i][j] = rmq[dp[i][j-1]] < rmq[dp[i+(1<<(j-1))][j-1]]?dp[i][j-1]:dp[i+(1<<(j-1))][j-1];
}
int query(int a,int b)//查询[a,b]之间最小值的下标
{
if(a > b)swap(a,b);
int k = mm[b-a+1];
return rmq[dp[a][k]] <= rmq[dp[b-(1<<k)+1][k]]?dp[a][k]:dp[b-(1<<k)+1][k];
}
};
//边的结构体定义
struct Edge
{
int to,next;
};
Edge edge[MAXN*2];
int tot,head[MAXN];
int F[MAXN*2];//欧拉序列,就是dfs遍历的顺序,长度为2*n-1,下标从1开始
int P[MAXN];//P[i]表示点i在F中第一次出现的位置
int cnt;
ST st;
void init()
{
tot = 0;
memset(head,-1,sizeof(head));
}
void addedge(int u,int v)//加边,无向边需要加两次
{
edge[tot].to = v;
edge[tot].next = head[u];
head[u] = tot++;
}
void dfs(int u,int pre,int dep)
{
F[++cnt] = u;
rmq[cnt] = dep;
P[u] = cnt;
for(int i = head[u];i != -1;i = edge[i].next)
{
int v = edge[i].to;
if(v == pre)continue;
dfs(v,u,dep+1);
F[++cnt] = u;
rmq[cnt] = dep;
}
}
void LCA_init(int root,int node_num)//查询LCA前的初始化
{
cnt = 0;
dfs(root,root,0);
st.init(2*node_num-1);
}
int query_lca(int u,int v)//查询u,v的lca编号
{
return F[st.query(P[u],P[v])];
}
bool flag[MAXN];
int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
int T;
int N;
int u,v;
scanf("%d",&T);
while(T--)
{
scanf("%d",&N);
init();
memset(flag,false,sizeof(flag));
for(int i = 1; i < N;i++)
{
scanf("%d%d",&u,&v);
addedge(u,v);
addedge(v,u);
flag[v] = true;
}
int root;
for(int i = 1; i <= N;i++)
if(!flag[i])
{
root = i;
break;
}
LCA_init(root,N);
scanf("%d%d",&u,&v);
printf("%d\n",query_lca(u,v));
}
return 0;
}
离线算法:DFS+并查集
通过DFS不断向下查询,直到叶节点,然后返回的过程中将子节点加入到父节点的并查集中。
详细过程见参考博客
/*
POJ 1330
离线算法
*/
#include<stdio.h>
#include<string.h>
#include<iostream>
#include<algorithm>
#include<math.h>
#include<vector>
using namespace std;
const int MAXN=10010;
int F[MAXN];//并查集
int r[MAXN];//并查集中集合的个数
bool vis[MAXN];//访问标记
int ancestor[MAXN];//祖先
struct Node
{
int to,next;
}edge[MAXN*2];
int head[MAXN];
int tol;
void addedge(int a,int b)
{
edge[tol].to=b;
edge[tol].next=head[a];
head[a]=tol++;
edge[tol].to=a;
edge[tol].next=head[b];
head[b]=tol++;
}
struct Query
{
int q,next;
int index;//查询编号
}query[MAXN*2];//查询数
int answer[MAXN*2];//查询结果
int cnt;
int h[MAXN];
int tt;
int Q;//查询个数
void add_query(int a,int b,int i)
{
query[tt].q=b;
query[tt].next=h[a];
query[tt].index=i;
h[a]=tt++;
query[tt].q=a;
query[tt].next=h[b];
query[tt].index=i;
h[b]=tt++;
}
void init(int n)
{
for(int i=1;i<=n;i++)
{
F[i]=-1;
r[i]=1;
vis[i]=false;
ancestor[i]=0;
tol=0;
tt=0;
cnt=0;//已经查询到的个数
}
memset(head,-1,sizeof(head));
memset(h,-1,sizeof(h));
}
int find(int x)
{
if(F[x]==-1)return x;
return F[x]=find(F[x]);
}
void Union(int x,int y)//合并
{
int t1=find(x);
int t2=find(y);
if(t1!=t2)
{
if(r[t1]<=r[t2])
{
F[t1]=t2;
r[t2]+=r[t1];
}
else
{
F[t2]=t1;
r[t1]+=r[t2];
}
}
}
void LCA(int u)
{
//if(cnt>=Q)return;//不要加这个
ancestor[u]=u;
vis[u]=true;//这个一定要放在前面
for(int i=head[u];i!=-1;i=edge[i].next)
{
int v=edge[i].to;
if(vis[v])continue;
LCA(v);
Union(u,v);
ancestor[find(u)]=u;
}
for(int i=h[u];i!=-1;i=query[i].next)
{
int v=query[i].q;
if(vis[v])
{
answer[query[i].index]=ancestor[find(v)];
cnt++;//已经找到的答案数
}
}
}
bool flag[MAXN];
int main()
{
//freopen("in.txt","r",stdin);
//freopen("out.txt","w",stdout);
int T;
int N;
int u,v;
scanf("%d",&T);
while(T--)
{
scanf("%d",&N);
init(N);
memset(flag,false,sizeof(flag));
for(int i=1;i<N;i++)
{
scanf("%d%d",&u,&v);
flag[v]=true;
addedge(u,v);
}
Q=1;//查询只有一组
scanf("%d%d",&u,&v);
add_query(u,v,0);//增加一组查询
int root;
for(int i=1;i<=N;i++)
if(!flag[i])
{
root=i;
break;
}
LCA(root);
for(int i=0;i<Q;i++)//输出所有答案
printf("%d\n",answer[i]);
}
return 0;
}