您的位置:首页 > 其它

POJ 1330 Nearest Common Ancestors 最近公共祖先模板/在线/离线

2013-05-28 21:53 633 查看
题意:求树上两个节点的最近公共祖先

算法一:tarjan

LCA(u) {

  Make-Set(u)

  ancestor[Find-Set(u)]=u //设置u所在集合的祖先

  对于u的每一个孩子v {

   LCA(v)

   Union(v,u) //把v生成的子集并入u中

   ancestor[Find-Set(u)]=u //防止采用树形启发式合并使u的集合根(代表)变化

  }

  checked[u]=true

  对于每个(u,v)属于P {

   if checked[v]=true

   then 回答u和v的最近公共祖先为 ancestor[Find-Set(v)]

  }

}

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;

/*
时间复杂度O(N+Q)
离线算法,必须先记录询问
对于每一对询问lac(u,v),在u,v的查询队列里各加一次
*/

const int MAXN = 10010;

class LCA_Tarjan
{
public:
int n, father[MAXN];
bool vis[MAXN];
vector<int> edge[MAXN];
vector<int> query[MAXN];

void init(int n)
{
for(int i = 1; i <= n; i++)
{
vis[i] = false;
edge[i].clear();
query[i].clear();
}
make_set(n);
}

void make_set(int n) //下标从1开始
{
for(int i = 1; i <= n; i++)
father[i] = i;
}

int find(int u)
{
if(u == father[u])
return u;
return father[u] = find(father[u]);
}

void Union(int u, int v) //可以优化
{
int fu = find(u);
int fv = find(v);
if(fv != fu)
father[fv] = fu;
}

void tarjan(int u)
{
father[u] = u;
int sz = edge[u].size();
for(int i = 0; i < sz; i++)
{
tarjan(edge[u][i]);
Union(u, edge[u][i]);
father[find(u)] = u;
}

vis[u] = true;
sz = query[u].size();
for(int i = 0; i < sz; i++)
{
if(vis[query[u][i]] == true)
cout << father[find(query[u][i])] << endl;
}
}
};

int main()
{
LCA_Tarjan t;
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d", &t.n);
t.init(t.n);

int u, v;
bool notRoot[MAXN] = {0};
for(int i = 1; i < t.n; i++)
{
scanf("%d %d", &u, &v);
t.edge[u].push_back(v);
notRoot[v] = true;
}

scanf("%d %d", &u, &v);
t.query[u].push_back(v);
t.query[v].push_back(u);
for(int i = 1; i <= t.n; i++)
if(notRoot[i] == false) {t.tarjan(i); break;}
}
return 0;
}


算法二LCA向RMQ的转化:

对有根树T进行DFS,将遍历到的结点按照顺序记下,我们将得到一个长度为2N – 1的序列,称之为T的欧拉序列F
每个结点都在欧拉序列中出现,我们记录结点u在欧拉序列中第一次出现的位置为pos(u)



根据DFS的性质,对于两结点u、v,从pos(u)遍历到pos(v)的过程中经过LCA(u, v)有且仅有一次,且深度是深度序列B[pos(u)…pos(v)]中最小的
即LCA(T, u, v) = RMQ(B, pos(u), pos(v)),并且问题规模仍然是O(N)的
这就证明了LCA问题可以转化成RMQ问题
LCA与RMQ问题可以互相转化,并且可以在O(N)的时间内完成!

/*
1.时间复杂度O(N*logN+Q)
2.在线算法
3.搜索之后n个节点得到2*n-1个编号
*/

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<vector>
using namespace std;

const int MAXN = 10010;
const int PP = 25;

class LCA_RMQ
{
public:
int n;
int pow2[PP]; //2^k
int tim; //时间戳,从1开始
int first[MAXN]; //节点第一次访问的时间戳
int nodeId[MAXN*2]; //与相应时间戳对应的节点编号
int dep[MAXN*2]; //深度
int dp[MAXN*2][PP]; //搜索之后得到2*n-1个编号
bool vis[MAXN]; //记录节点是否被访问过
vector<int> edge[MAXN];

void init(int n)
{
for(int i = 0; i < PP; i++)
pow2[i] = (1<<i);

for(int i = 1; i <= n; i++)
{
edge[i].clear();
vis[i] = false;
}

}

void addedge(int u ,int v)
{
edge[u].push_back(v);
}

void dfs(int u ,int d) //u是节点, d是深度
{
tim++;  //时间戳+1
vis[u] = true;
nodeId[tim] = u;   //记录与时间戳相对应的节点
first[u] = tim; //记录下节点u首次访问的时间戳
dep[tim] = d;  //根节点的深度为1
int sz = edge[u].size();
for(int i = 0; i < sz; i++)
{
int v = edge[u][i];
if(vis[v] == false)
{
dfs(v, d + 1);
tim++;
nodeId[tim] = u;  //只要访问的不是叶子节点,tim都要重复增加
dep[tim] = d;
}
}
}

void ST(int len)
{
int k = (int)(log(len+1.0) / log(2.0));

for(int i = 1; i <= len; i++)
dp[i][0] = i;

for(int j = 1; j <= k; j++)
for(int i = 1; i + pow2[j] - 1 <= len; i++)
{
int a = dp[i][j-1]; //a, b均为下标
int b = dp[i+pow2[j-1]][j-1];
if(dep[a] < dep[b]) dp[i][j] = a;
else dp[i][j] = b;
}
}

int RMQ(int x ,int y)
{
int k = (int)(log(y-x+1.0) / log(2.0));
int a = dp[x][k];
int b = dp[y-pow2[k]+1][k];
if(dep[a] < dep[b]) return a;
else return b;
}

int LCA(int u ,int v)
{
int x = first[u];
int y = first[v];
if(x > y) swap(x,y);
int index = RMQ(x,y);
return nodeId[index];
}
};

LCA_RMQ t;

int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int u, v, rt;
bool notRoot[MAXN] = {0};
scanf("%d",&t.n);
t.init(t.n);
for(int i = 1; i < t.n; i++)
{
scanf("%d %d",&u,&v);
t.addedge(u, v);
notRoot[v] = true;
}
scanf("%d %d", &u, &v);

for(int i = 1; i <= t.n; i++)
if(notRoot[i] == false) {rt = i; break;}

t.tim = 0;
t.dfs(rt, 1); //根节点深度为1
t.ST(2 * t.n - 1);
int ans = t.LCA(u, v);
printf("%d\n", ans);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: