• 《挑战程序设计竞赛》2.4 数据结构-并查集 POJ1182 2236 1703 AOJ2170


    POJ1182

    http://poj.org/problem?id=1182

    题目

    难得的中文题。。。
    食物链
    Time Limit: 1000MS Memory Limit: 10000K
    Total Submissions: 56252 Accepted: 16485
    Description
    动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B, B吃C,C吃A。
    现有N个动物,以1-N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。
    有人用两种说法对这N个动物所构成的食物链关系进行描述:
    第一种说法是”1 X Y”,表示X和Y是同类。
    第二种说法是”2 X Y”,表示X吃Y。
    此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
    1) 当前的话与前面的某些真的话冲突,就是假话;
    2) 当前的话中X或Y比N大,就是假话;
    3) 当前的话表示X吃X,就是假话。
    你的任务是根据给定的N(1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。
    Input
    第一行是两个整数N和K,以一个空格分隔。
    以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。
    若D=1,则表示X和Y是同类。
    若D=2,则表示X吃Y。
    Output
    只有一个整数,表示假话的数目。
    Sample Input
    100 7
    1 101 1
    2 1 2
    2 2 3
    2 3 3
    1 1 3
    2 3 1
    1 5 5
    Sample Output
    3

    思路

    对于每只动物i创建3个元素i-A,i-B,i-C,并用3×N个元素建立并查集,维护如下信息:

    • i-X表示i属于X
    • 如果i-A和j-B在同一个组里,那么如果i属于A,j就一定属于B。如果j属于B,i就一定属于A。

    我们对每条信息进行如下操作:

    • 如果x或者y比N大或者小于1,则答案+1
    • x和y属于同一种类。如果已知x和y不属于同一类,则答案+1。否则合并x-A和y-A、x-B和y-B、x-C和y-C
    • x吃y。如果已知x和y属于同一类或者x-A和y-C在同一个集合,则答案+1。否则合并x-A和y-B、x-B和y-C、x-C和y-A

    最后,输出答案。
    需要注意的是一定要有路径压缩,即使用rank平衡各树的长度,否则很有可能超时。

    还有一种解法是带权并查集,参见http://blog.csdn.net/balloons2012/article/details/7871104

    代码

    Source Code
    
    Problem: 1182       User: liangrx06
    Memory: 1368K       Time: 266MS
    Language: C++       Result: Accepted
    Source Code
    #include <iostream>
    #include <cstdio>
    using namespace std;
    
    const int N = 50000;
    
    int n;
    int pre[3*N+1];
    int rank[3*N+1];
    
    void init()
    {
        for (int i = 1; i <= 3*n; i ++) {
            pre[i] = i;
            rank[i] = 0;
        }
    }
    
    int find(int a)
    {
        while (a != pre[a])
            a = pre[a];
        return a;
    }
    
    void unite(int a, int b)
    {
        a = find(a);
        b = find(b);
        if (a == b) return;
        if (rank[a] < rank[b]) {
            pre[a] = b;
        } else {
            pre[b] = a;
            if (rank[a] == rank[b])
                rank[a] ++;
        }
    }
    
    bool same(int a, int b)
    {
        return find(a) == find(b);
    }
    
    int main(void)
    {
        int k;
        int d, a, b;
        int res = 0;
    
        cin >> n >> k;
        init();
        while ( k-- ) {
            scanf("%d%d%d", &d, &a, &b);
            if ( a > n || b > n ) {
                res ++;
            }
            else if ( d == 1 ) {
                if ( same(a, b+n) || same(a, b+2*n) ) {
                    res ++;
                } else {
                    unite(a, b);
                    unite(a+n, b+n);
                    unite(a+2*n, b+2*n);
                }
            }
            else {
                if ( same(a, b) || same(a, b+2*n) ) {
                    res ++;
                } else {
                    unite(a, b+n);
                    unite(a+n, b+2*n);
                    unite(a+2*n, b);
                }
            }
        }
        printf("%d
    ", res);
    
        return 0;
    }

    POJ2236

    http://poj.org/problem?id=2236

    题意

    一张图上分布着n台坏了的电脑,并知道它们的坐标。两台修好的电脑如果距离<=d就可以联网,也可以通过其他修好的电脑间接相连。给出操作“O x”表示修好x,给出操作“S x y”,请你判断x和y在此时有没有连接上。

    思路

    基本并查集题目,每次O x都对所有与x相连的电脑做更新,加入并查集,S x y时直接判断是否相连即可。
    我的代码是书中所给并查集的标准实现。

    代码

    Source Code
    
    Problem: 2236       User: liangrx06
    Memory: 296K        Time: 1500MS
    Language: C++       Result: Accepted
    Source Code
    #include <iostream>
    #include <cstdio>
    #include <set>
    #include <string>
    #include <cmath>
    using namespace std;
    
    const int N = 1001;
    
    struct Node {
        int x, y;
    };
    
    int n, d;
    Node node[N+1];
    int pre[N+1];
    int rank[N+1];
    
    void init()
    {
        for (int i = 1; i <= n; i ++) {
            pre[i] = i;
            rank[i] = 0;
        }
    }
    
    int find(int a)
    {
        while (a != pre[a])
            a = pre[a];
        return a;
    }
    
    void unite(int a, int b)
    {
        a = find(a);
        b = find(b);
        if (a == b) return;
        if (rank[a] < rank[b]) {
            pre[a] = b;
        } else {
            pre[b] = a;
            if (rank[a] == rank[b])
                rank[a] ++;
        }
    }
    
    bool same(int a, int b)
    {
        return find(a) == find(b);
    }
    
    void input()
    {
        cin >> n >> d;
        for (int i = 1; i <= n; i ++) {
            scanf("%d%d", &node[i].x, &node[i].y);
        }
    }
    
    double dis(int a, int b)
    {
        int x2 = (node[a].x-node[b].x) * (node[a].x - node[b].x);
        int y2 = (node[a].y-node[b].y) * (node[a].y - node[b].y);
        return sqrt((double)(x2 + y2));
    }
    
    void solve()
    {
        string s;
        int a, b;
        set<int> rep;
    
        while ( cin >> s ) {
            if (s == "O") {
                scanf("%d", &a);
                if ( rep.find(a) == rep.end() ) {
                    set<int>::iterator it;
                    for (it = rep.begin(); it != rep.end(); it ++) {
                        if ( dis(a, *it) <= d ) {
                            //printf("unite %d %d
    ", a, *it);
                            unite(a, *it);
                        }
                    }
                    rep.insert(a);
                }
            } else {
                scanf("%d%d", &a, &b);
                //printf("%d %d %d %d
    ", a, b, find(a), find(b));
                if ( same(a, b) )
                    printf("SUCCESS
    ");
                else
                    printf("FAIL
    ");
            }
        }
    }   
    
    int main(void)
    {   
        input();
        init();
        solve();
    
        return 0;
    } 

    POJ1703

    http://poj.org/problem?id=1703

    题意

    在这个城市里有两个黑帮团伙,现在给出N个人,问任意两个人他们是否在同一个团伙。
    输入D x y代表x于y不在一个团伙里。
    输入A x y要输出x与y是否在同一团伙或者不确定他们在同一个团伙里。

    思路

    这个题是poj1182的简单版,具体不再赘述。

    代码

    Source Code
    
    Problem: 1703       User: liangrx06
    Memory: 1772K       Time: 344MS
    Language: C++       Result: Accepted
    Source Code
    #include <iostream>
    #include <cstdio>
    using namespace std;
    
    const int N = 100000;
    
    int n;
    int pre[2*N+1];
    int rank[2*N+1];
    
    void init()
    {
        for (int i = 1; i <= 2*n; i ++) {
            pre[i] = i;
            rank[i] = 0;
        }
    }
    
    int find(int a)
    {
        while (a != pre[a])
            a = pre[a];
        return a;
    }
    
    void unite(int a, int b)
    {
        a = find(a);
        b = find(b);
        if (a == b) return;
        if (rank[a] < rank[b]) {
            pre[a] = b;
        } else {
            pre[b] = a;
            if (rank[a] == rank[b])
                rank[a] ++;
        }
    }
    
    bool same(int a, int b)
    {
        return find(a) == find(b);
    }
    
    int main(void)
    {
        int t, m;
        char s[2];
        int a, b;
    
        cin >> t;
        while ( t-- ) {
            cin >> n >> m;
            init();
            while ( m-- ) {
                scanf("%s%d%d", s, &a, &b);;
                if ( s[0] == 'D' ) {
                    unite(a, b+n);
                    unite(a+n, b);
                } else {
                    if ( same(a, b) )
                        printf("In the same gang.
    ");
                    else if ( same(a, b+n) )
                        printf("In different gangs.
    ");
                    else
                        printf("Not sure yet.
    ");
                }
            }
        }
    
        return 0;
    }

    AOJ2170

    http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=2170

    题意

    给定一棵树,对于这棵树有两个操作,M i表示将i的所有子孙节点以i为树根从原树中分离出来,Q i表示询问节点i的树根是多少。

    思路

    用数组pre[i]记录节点i的父亲节点,若i是根节点就记为i,这个题中1是根节点。
    这样对于两种操作我们只要如下处理即可:

    • M i:将tree[i]改为它本身,也就是i
    • Q i:从i向上查找直到找到它的根节点

    代码

    #include <iostream>
    #include <cstdio>
    using namespace std;
    
    const int N = 100000;
    
    int n, p;
    int pre[N+1];
    
    int find(int x)
    {
        while (x != pre[x])
            x = pre[x];
        return x;
    }
    
    int main(void)
    {
        while (scanf("%d%d", &n, &p) != EOF) {
            if (!n && !p) break;
            pre[1] = 1;
            for (int i = 2; i <= n; i ++)
                scanf("%d", &pre[i]);
            long long sum = 0;
            for (int i = 1; i <= p; i ++) {
                char s[2];
                int x;
                scanf("%s%d", s, &x);
                if (s[0] == 'M')
                    pre[x] = x;
                else
                    sum += find(x);
            }
            printf("%lld
    ", sum);
        }
    
        return 0;
    }
  • 相关阅读:
    利用Airtest开发爬虫
    添加mitmproxy证书为安卓系统证书(模拟器亦可)
    mac下 安卓模拟器抓包推荐
    mac上appium连接ios
    HomeBrew和HomeBrew Cask的安装和使用
    NOIP2020 游记
    CSP2020 游记
    随机数之 xorShift128Plus 板子
    最大单词长度乘积(力扣第318题)
    两个整数之和(力扣第371题)
  • 原文地址:https://www.cnblogs.com/liangrx06/p/5083764.html
Copyright © 2020-2023  润新知