poj-1330

来源:互联网 发布:支持java的虚拟主机 编辑:程序博客网 时间:2024/06/06 02:53
//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过程。

0 0
原创粉丝点击