- LCA
- 在线算法 RMQ之ST加DFS
- 离线算法Tarjan加DFS
以poj1330为例 题意:T组测试案例,m个节点,m-1条变,最后一个询问;问u,v的最近公共祖先;
学习资料:http://blog.csdn.net/shahdza/article/details/7689338
http://www.cnblogs.com/kuangbin/archive/2012/09/21/2697395.html
数组含义,摘抄于资料
注意LCAT(u, v)是在对T进行dfs过程当中在访问u和v之间离根结点最近的点。因此我们可以考虑树的欧拉环游过程u和v之间所有的结点,并找到它们之间处于最低层的结点。为了达到这个目的,我们可以建立三个数组:
E[1, 2*N-1] - 对T进行欧拉环游过程中所有访问到的结点;E[i]是在环游过程中第i个访问的结点
L[1,2*N-1] - 欧拉环游中访问到的结点所处的层数;L[i]是E[i]所在的层数
H[1, N] ,H[i] - 是E中结点i第一次出现的下标(任何出现i的地方都行,当然选第一个不会错)假定H[u]<H[v](否则你要交换u和v)。可以很容易的看到u和v第一次出现的结点是E[H[u]..H[v]]。现在,我们需要找到这些结点中的最低层。为了达到这个目的,我们可以使用RMQ。因此 LCAT(u, v) = E[RMQL(H[u],H[v])] (记住RMQ返回的是索引),
在线算法:DFS+RMQ的ST算法
#include<iostream>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
#include<set>
#include<stack>
#define cl(a,b) memset(a,b,sizeof(a));
#define LL long long
#define P pair<int,int>
#define X first
#define Y second
#define pb push_back
using namespace std;
const int maxn=10010;
const int inf=9999999;
const int mod=100007;
int L[maxn<<1];///编号1-n,注意编号
struct ST{///RMQ的ST算法,返回最小元素的下标
int mm[maxn<<1];//最高位1的位置mm[0]=-1,mm[1]=0,mm[2]=2;
int dp[maxn<<1][20];
void init(int n){
mm[0]=-1;
for(int i=1;i<=n;i++){
dp[i][0]=i;
mm[i]=(i&(i-1))==0?mm[i-1]+1:mm[i-1];
}
for(int j=1;j<=mm[n];j++){
for(int i=1;i+(1<<j)-1<=n;i++){
dp[i][j]=L[dp[i][j-1]]<L[dp[i+(1<<(j-1))][j-1]]?dp[i][j-1]:dp[i+(1<<(j-1))][j-1];
}
}
}
int query(int i,int j){
if(i>j)swap(i,j);
int k=mm[j-i+1];
return L[dp[i][k]]<L[dp[j-(1<<k)+1][k]]?dp[i][k]:dp[j-(1<<k)+1][k];
}
};
struct node{//边的结构,邻接表
int to,next;
};
struct LCA{
int n;
int head[maxn<<1];
int tol;
node edge[maxn<<1];//边的相关数组
int E[maxn<<1];//欧拉环游数组,i出现的顺序
int H[maxn];//i第一次出现在E数组中的位置
ST st;
void init(int n){
this->n=n;
tol=0;
memset(head,-1,sizeof(head));
}
void addedge(int u,int v){//加边
edge[tol].to=v;
edge[tol].next=head[u];
head[u]=tol++;
edge[tol].to=u;
edge[tol].next=head[v];
head[v]=tol++;
}
bool vis[maxn];//DFS标志数组
int cnt;//访问的编号
void dfs(int x,int level){
vis[x]=true;
E[++cnt]=x;
L[cnt]=level;
H[x]=cnt;
for(int i=head[x];i!=-1;i=edge[i].next){
int v=edge[i].to;
if(vis[v])continue;
dfs(v,level+1);
E[++cnt]=x;
L[cnt]=level;
}
}
int query(int a,int b){
return E[st.query(H[a],H[b])];
}
void solve(int root){
cnt=0;
memset(vis,false,sizeof(vis));
dfs(root,0);
st.init(n*2-1);//数组的长度,要注意
}
}lca;
bool flag[maxn];
int main(){
int T;
scanf("%d",&T);
while(T--){
int n;
scanf("%d",&n);
memset(flag,false,sizeof(flag));
lca.init(n);
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
lca.addedge(u,v);
flag[v]=true;
}
int root;
for(int i=1;i<=n;i++)if(!flag[i]){
root=i;break;
}
lca.solve(root);
int l,r;
scanf("%d%d",&l,&r);
printf("%d\n",lca.query(l,r));
}
return 0;
}
离线算法:Tarjan算法,DFS+并查集 时间 O(n+q)线性复杂度,n是元素个数,q是询问次数
//伪代码
//parent为并查集,FIND为并查集的查找操作
//QUERY为询问结点对集合
//TREE为有根树
Tarjan(u)//从根节点开始
for each (u, v) in TREE
Tarjan(v)
parent[v] = u
visit[u] = true
for each (u, v) in QUERY
if visit[v]
ans(u, v) = FIND(v)
离线算法的学习资料:http://noalgo.info/476.html
算法从根节点root开始搜索,每次递归搜索所有的子树,然后处理跟当前根节点相关的所有查询。
算法用集合表示一类节点,这些节点跟集合外的点的LCA都一样,并把这个LCA设为这个集合的祖先。当搜索到节点x时,创建一个由x本身组成的集合,这个集合的祖先为x自己。然后递归搜索x的所有儿子节点。当一个子节点搜索完毕时,把子节点的集合与x节点的集合合并,并把合并后的集合的祖先设为x。因为这棵子树内的查询已经处理完,x的其他子树节点跟这棵子树节点的LCA都是一样的,都为当前根节点x。所有子树处理完毕之后,处理当前根节点x相关的查询。遍历x的所有查询,如果查询的另一个节点v已经访问过了,那么x和v的LCA即为v所在集合的祖先。
其中关于集合的操作都是使用并查集高效完成。
算法的复杂度为,O(n)搜索所有节点,搜索每个节点时会遍历这个节点相关的所有查询。如果总的查询个数为m,则总的复杂度为O(n+m)。
POJ1330
基于伪代码实现的代码
#include<iostream>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
#include<set>
#include<stack>
#define cl(a,b) memset(a,b,sizeof(a));
#define LL long long
#define P pair<int,int>
#define X first
#define Y second
#define pb push_back
using namespace std;
const int maxn=10010;
const int inf=9999999;
const int mod=100007;
bool flag[maxn];//找根节点的数组
struct que{//查询结构体
int q,index;
que(){}
que(int a,int b):q(a),index(b){}
};
vector<que> query[maxn];//存储查询
vector<int> tree[maxn];//存树
int ans[maxn];//存答案
struct Tarjan{//tarjan算法
int f[maxn];//并查集
bool vis[maxn];//dfs标记数组
void init(int n){//初始化操作
for(int i=0;i<=n;i++){
f[i]=i;
vis[i]=false;
flag[i]=false;
tree[i].clear();
query[i].clear();
}
}
int Find(int x){//并查集查找函数,这是路径压缩的
return f[x]==x?x:f[x]=Find(f[x]);
}
void dfs(int u){//dfs遍历,进行查询
int n=tree[u].size();
for(int i=0;i<n;i++){
int v=tree[u][i];
dfs(v);
f[v]=u;
}
vis[u]=true;//遍历完节点u的所有子节点,就可以解答出与u有关的询问了
n=query[u].size();
for(int i=0;i<n;i++){
int v=query[u][i].q;
if(vis[v]){
ans[query[u][i].index]=Find(v);
}
}
}
void solve(int root){
dfs(root);
}
}tar;
int main(){
int T;
scanf("%d",&T);
while(T--){
int n;
scanf("%d",&n);
tar.init(n);
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
tree[u].pb(v);
flag[v]=true;
}
int Q=1;
for(int i=0;i<Q;i++){
int u,v;
scanf("%d%d",&u,&v);
query[u].pb(que(v,i));
query[v].pb(que(u,i));///注意查询交换
}
for(int i=1;i<=n;i++)if(!flag[i]){
tar.solve(i);break;
}
for(int i=0;i<Q;i++){
printf("%d\n",ans[i]);
}
}
return 0;
}
再写一个,和资料里讲解的一样的,二者不同在于下面多了个ancestor数组,见注释的**部分
#include<iostream>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
#include<set>
#include<stack>
#define cl(a,b) memset(a,b,sizeof(a));
#define LL long long
#define P pair<int,int>
#define X first
#define Y second
#define pb push_back
using namespace std;
const int maxn=10010;
const int inf=9999999;
const int mod=100007;
bool flag[maxn];
struct que{
int q,index;
que(){}
que(int a,int b):q(a),index(b){}
};
vector<que> query[maxn];
vector<int> tree[maxn];
int ans[maxn];
struct Tarjan{
int f[maxn];//并查集
bool vis[maxn];//dfs标记数组
int ancestor[maxn];//祖先
void init(int n){
for(int i=0;i<=n;i++){
f[i]=i;
vis[i]=false;
ancestor[i]=i;
flag[i]=false;
tree[i].clear();
query[i].clear();
}
}
int Find(int x){
return f[x]==x?x:f[x]=Find(f[x]);
}
void Union(int x,int y){
x=Find(x),y=Find(y);
if(x!=y)f[y]=x;
}
void dfs(int u){
int n=tree[u].size();
for(int i=0;i<n;i++){
int v=tree[u][i];
dfs(v);
Union(u,v);//*********
ancestor[Find(u)]=u;//*********
}
vis[u]=true;
n=query[u].size();
for(int i=0;i<n;i++){
int v=query[u][i].q;
if(vis[v]){
ans[query[u][i].index]=ancestor[Find(v)];//*********
}
}
}
void solve(int root){
dfs(root);
}
}tar;
int main(){
int T;
scanf("%d",&T);
while(T--){
int n;
scanf("%d",&n);
tar.init(n);
for(int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
tree[u].pb(v);
flag[v]=true;
}
int Q=1;
for(int i=0;i<Q;i++){
int u,v;
scanf("%d%d",&u,&v);
query[u].pb(que(v,i));
query[v].pb(que(u,i));///注意查询交换
}
for(int i=1;i<=n;i++)if(!flag[i]){
tar.solve(i);break;
}
for(int i=0;i<Q;i++){
printf("%d\n",ans[i]);
}
}
return 0;
}