http://poj.org/problem?id=1330
题意:给你n个节点和n-1个关系,通过关系建起一棵树,然后给你两个节点,求他们的最小公共祖先。
ps:这里的Tarjan离线算法貌似和前些日子做过的用Tarjan判断强连通不一样,难道Tarjan发明的算法都叫Tarjan算法么,那么他发明了应该不止一种的话。。。细思恐极
思路:这题是树和并查集的完美结合,边信息用头插法保存。首先说下LCA离线算法思想(复制别人的):
“离线算法需要预先将所有的询问存起来,等到dfs的时候一下逐一输出结果。
dfs对树进行遍历,当某个节点u的一个子树全部遍历完以后,就更新这棵子树的节点的父节点为u(转化为并查集:将该子树集合加入到u所在的集合中,并更新子树集合的父亲为u)如果当前节点x是被询问到的节点,并且一直相关的(询问了x,y的最近公共祖先)另一个节y点已经搜索过,则x与y的最近公共祖先为y此时的父亲。”
结合此博客的图,我们可以看出只要遍历过的未得到结果的子树,都会变为集合中的一部分,而集合中元素都以该集合为子树的根为根节点。于是巧妙的地方就出现了,这些子树所指向的根节点,一直都在查找的关键路径上!模拟一下就会发现,这个树是会变化的,就好像树在开花一样= =,(我把这里变为集合的操作称为开花,是不是有点像),这样叶子都聚集在关键路径上,一旦查找到当前元素且对应元素已被访问,那对应元素的根节点即为最小公共祖先。
#include <stdio.h>
#include <algorithm>
#include <stdlib.h>
#include <string.h>
#include <iostream>
using namespace std;
typedef __int64 LL;
const int N = 100010;
const int INF = 0x3f3f3f3f;
int pre[N], in[N], head[N], pos, x, y, n;
bool vis[N];
struct node
{
int from, to, next;
}edge[N];
void add(int u, int v)
{
edge[pos] = (struct node){u, v, head[u]};
head[u] = pos++;
}
int Find(int x)
{
if(x == pre[x]) return x;
pre[x] = Find(pre[x]);
return pre[x];
}
void LCA(int u)
{
pre[u] = u;
for(int i = head[u]; i != -1; i = edge[i].next)
{
LCA(edge[i].to);
pre[edge[i].to] = u;
}
vis[u] = true;
if(u == x && vis[y])
{
printf("%d\n", Find(y));
return;
}
if(u == y && vis[x])
{
printf("%d\n", Find(x));
return;
}
}
void init()
{
pos = 0;
for(int i = 1; i <= n; i++)
pre[i] = i;
memset(head, -1, sizeof(head));
memset(vis, false, sizeof(vis));
memset(in, 0, sizeof(in));
}
int main()
{
// freopen("in.txt", "r", stdin);
int t, u, v;
scanf("%d", &t);
while(t--)
{
scanf("%d", &n);
init();
for(int i = 1; i < n; i++)
{
scanf("%d%d", &u, &v);
add(u, v);
in[v]++;
}
scanf("%d%d", &x, &y);
for(int i = 1; i <= n; i++)
{
if(in[i] == 0) LCA(i);
}
}
return 0;
}