//492K 32MS C++
#include <cstdio>
#include <cstring>
const int MAX = 10005;
struct TreeNode {
int NextBroId;
int parentId;
};
struct Query {
int nodeAId;
int nodeBId;
};
typedef struct TreeNode TreeNode;
typedef struct Query Query;
int QueryNodeAId;
int QueryNodeBId;
int childListHead[MAX];
int UF_Ancestor[MAX];
int caseNum;
int nodeNum;
TreeNode tree[MAX];
void insertIntoChildList(int parentId, int childId) {
tree[childId].parentId = parentId;
int prevChildListHeadId = childListHead[parentId];
tree[childId].NextBroId = prevChildListHeadId;
childListHead[parentId] = childId;
}
int UF_find(int nodeId) {
// printf("UF_find %d\n", nodeId);
int parentNodeUFId = nodeId;
if (UF_Ancestor[parentNodeUFId] != parentNodeUFId) {
UF_Ancestor[nodeId] = UF_find(UF_Ancestor[parentNodeUFId]);
return UF_Ancestor[nodeId];
} else {
return parentNodeUFId;
}
}
void UF_Merge(int parentId, int childId) {
int parentUFId = UF_find(parentId);
int childUFId = UF_find(childId);
UF_Ancestor[childUFId] = parentUFId;
UF_Ancestor[childId] = parentUFId;
}
int res;
int DFSWithUF(int curNodeId) {
// printf("DFSWithUF %d\n", curNodeId);
UF_Ancestor[curNodeId] = curNodeId;
int curChildNodeId = childListHead[curNodeId];
while(curChildNodeId) {
if (DFSWithUF(curChildNodeId)) {
return 1;
}
UF_Merge(curNodeId, curChildNodeId);
curChildNodeId = tree[curChildNodeId].NextBroId;
}
if (curNodeId == QueryNodeAId && UF_Ancestor[QueryNodeBId] != 0) {
res = UF_find(QueryNodeBId);
// printf("res is UF_find(%d)%d\n", res, QueryNodeBId);
return 1;
}
if (curNodeId == QueryNodeBId && UF_Ancestor[QueryNodeAId] != 0) {
res = UF_find(QueryNodeAId);
// printf("res is UF_find(%d)%d\n", res, QueryNodeAId);
return 1;
}
return 0;
}
int main() {
scanf("%d", &caseNum);
for (int i = 1; i <= caseNum; i++) {
scanf("%d", &nodeNum);
memset(childListHead, 0, sizeof(childListHead));
memset(tree, 0, sizeof(tree));
memset(UF_Ancestor, 0, sizeof(UF_Ancestor));
res = 0;
for (int edgeId = 1; edgeId <= nodeNum-1; edgeId++) {
int parentId;
int childId;
scanf("%d %d\n", &parentId, &childId);
insertIntoChildList(parentId, childId);
}
int rootNodeId = 0;
for (int i = 1; i <= nodeNum; i++) {
if (tree[i].parentId == 0) {
rootNodeId = i;
break;
}
}
// printf("Root: %d\n", rootNodeId);
scanf("%d %d", &QueryNodeAId, &QueryNodeBId);
// printf("Query %d %d\n", QueryNodeAId, QueryNodeBId);
DFSWithUF(rootNodeId);
printf("%d\n", res);
}
}
第一道LCA基础题,用的是离线算法,以前见过这道题,不过当时想的就是得到从根节点到两个点的路径,然后遍历比较得到最深父节点。
今天才知道这道题可以用dfs+并查集搞,
其实code不复杂,但是想通原理还是花了些功夫,有些地方还是比较绕的, 不太好描述,
每次DFS到某个点K,那么该点的并查集Id(UF_Ancestor[K])就暂时设为K, 因为从K继续DFS的点,都是K的子孙节点,这些点和K的LCA一定就是K,也就是K的并查集Id,
而对于和K一个parent的 其他兄弟节点以及其子孙节点, 其和K 的 LCA就是K的parent, 在向上, 和K的parent是一个parent的其他兄弟节点及其子孙节点,和K的LCA就是K的parent的parent......, 如何表述这种关系,就是用DFS。
举个例子: 找到2 和 7的LCA
1
2 3
4 5 6 7
当DFS到2的时候,2的子节点,4和5 和 2的LCA都是2, 所以这时候,在DFS还没有从以2为根的子树脱出的前提下, 2的UF_Ancestor就是 2,任何 2下面的子孙节点和2的LCA就是2的UF_Ancestor(2就是此子树集合的最低入口), 在对 4 和 5 DFS以后, 虽然找到了2, 但是没有找到7,那么4 和 5的并查集Id就要更新为2了(这个时候,2和4,5变成了并查集的一个集合, 并且此集合的root是2, 可以这么理解, 在要从2脱出回溯的情况下,对于其他不是2 和 2子孙节点的点,2,4,5就是一个整体,并且2 是 进入 4, 5的入口), 因此,要从以2为根的子树脱出回溯到1,DFS, 2的父节点 1 的下一个子节点,也就是 3,
这个时候,在以1为根的子树(其实是完整树了)中, 任何不在以2为根的子树内的节点 和 2 以及其子树的节点 的 LCA 都是 1(因为不管怎么样都不可能不经过1,进入2为根的子树), 这个时候,就像之前的4,5一样,2 的 并查集Id更新为1,将以2为根的集合加入到 以1为根的集合内,此时,任何要访问 2 , 4 , 5的其他节点,都要经过入口1,接着DFS到3, 继续到6,6不是7,向上回溯到3,再dfs 7,此时,访问到了7, 而又发现,之前的2已经被dfs过了,并且,在没有从1脱出回溯的情况下,进入2的入口(也就是2所属集合的root)就是 1, 因此,1就是 2和 7的LCA。
如果在1上面再加一层: 改为求 2和 9的LCA,
8
1 9
2 3
4 5 6 7
那么因为,3 , 6, 7都不是9,因此对1的dfs结束,要回溯到 8, 此时, 1 到 8 在外界就全部视为一个集合,该集合的入口是 8, 在dfs 8 的下一个子节点, 9时, 发现2已经被dfs过,且2所属集合的入口是 8, 那么 2和 LCA就是 8.
还是有点绕,DFS本身就有点抽象,再加上并查集,确实不是很清晰,跟直观的线性思维不大一样.
http://kmplayer.iteye.com/blog/604518
分类,使每个结点都落到某个类中,到时候只要执行集合查询,就可以知道结点的LCA了。
对于一个结点u.类别有:
以u为根的子树、除类一以外的以f(u)为根的子树、除前两类以外的以f(f(u))为根的子树、除前三类以外的以f(f(f(u)))为根的子树……
类一的LCA为u,类二为f(u),类三为f(f(u)),类四为f(f(f(u)))。这样的分类看起来好像并不困难。
但关键是查询是二维的,并没有一个确定的u。接下来就是这个算法的巧妙之处了。
利用递归的LCA过程。