poj-1330

7 篇文章 0 订阅
3 篇文章 0 订阅
//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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值