zoukankan      html  css  js  c++  java
  • P1525 [NOIP2010 提高组] 关押罪犯

    题目传送门

    一、种类并查集模板题

    学习网站:https://zhuanlan.zhihu.com/p/97813717

    一般的并查集,维护的是具有连通性、传递性的关系,例如亲戚的亲戚是亲戚。但是,有时候,我们要维护另一种关系:敌人的敌人是朋友。种类并查集就是为了解决这个问题而诞生的。

    其实很容易想到,本题可以贪心,把所有矛盾关系从大到小排个序,然后尽可能地把矛盾大的犯人关到不同的监狱里,直到不能这么做为止。这看上去可以用并查集维护,但是有一个问题:我们得到的信息,不是哪些人应该在相同的监狱,而是哪些人应该在不同的监狱。这怎么处理呢?这个题其实有很多做法,但这里,我们介绍使用种类并查集的做法。

    我们开一个两倍大小的并查集。例如,假如我们要维护4个元素的并查集,我们改为开8个单位的空间:

    我们用(1 sim 4)维护朋友关系(就这道题而言,是指关在同一个监狱的狱友),用(5 sim 8)维护敌人关系(这道题里是指关在不同监狱的仇人)。现在假如我们得到信息:(1)(2)是敌人,应该怎么办?

    我们(join(1, 2+n), join(1+n, 2));。这里(n)就等于(4),但我写成(n)这样更清晰。对于1个编号为(i)的元素,(i+n)是它的敌人。所以这里的意思就是:(1)(2)的敌人,(2)(1)的敌人。

    现在假如我们又知道(2)(4)是敌人,我们(join(2, 4+n), join(2+n, 4))

    发现了吗,敌人的敌人就是朋友,(2)(4)是敌人,(2)(1)也是敌人,所以这里,(1)(4)通过(2+n)这个元素间接地连接起来了。这就是种类并查集工作的原理。

    二、相关例题

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

    三、C++代码

    #include <bits/stdc++.h>
    
    using namespace std;
    const int N = 20010;
    const int M = 100010;
    
    int n, m, x, y;
    
    //fa[i]表示i所属的集合,用fa[i+n]表示i所属集合的补集,实现的很巧妙,可以当成一个使用并查集的巧妙应用;
    int fa[N << 1];  //并查集数组,有时也写成 N << 1 ,从左到右,读作N左移1位,就是 2*N
    
    //要深入理解这个递归并压缩的过程
    int find(int x) {
        if (fa[x] != x) fa[x] = find(fa[x]);
        return fa[x];
    }
    
    //加入家族集合中
    void join(int c1, int c2) {
        int f1 = find(c1), f2 = find(c2);
        if (f1 != f2)fa[f1] = f2;
    }
    
    //关系
    struct node {
        int x; //第一个人
        int y; //第二个人
        int v; //冲突值
    } a[M];
    
    //排序函数,按冲突值大小排序,大的在前
    bool cmp(const node &a, const node &b) {
        return a.v > b.v;
    }
    
    int main() {
        //读入
        cin >> n >> m;
        for (int i = 1; i <= m; i++) cin >> a[i].x >> a[i].y >> a[i].v;
        //排序
        sort(a + 1, a + m + 1, cmp);
    
        //初始化并查集,每个人都是自己的祖先
        for (int i = 1; i <= 2 * n; i++)fa[i] = i;//注意这里是2*n
    
        //处理m组关系
        for (int i = 1; i <= m; i++) {
            x = find(a[i].x);
            y = find(a[i].y);
            if (x == y) {
                cout << a[i].v << endl;
                return 0;
            } else
                join(a[i].y + n, x), join(a[i].x + n, y);
        }
        cout << 0 << endl;
        return 0;
    }
    
  • 相关阅读:
    太有才了!街头创意涂鸦手绘图片欣赏【上篇】
    设计前沿:25款精妙的 iOS 应用程序图标
    Web 前端开发精华文章集锦(jQuery、HTML5、CSS3)【系列十八】
    神奇的世界!那些使用食物创造出来的景观【组图】
    Screenfly – 各种设备的屏幕和分辨率下快速测试网站
    经典网页设计:30个新鲜出炉的扁平化网站设计《下篇》
    40款很奇异的名片设计,吸引你的眼球《下篇》
    推荐25款实用的 HTML5 前端框架和开发工具【下篇】
    CSS3 Animation Cheat Sheet:实用的 CSS3 动画库
    今日推荐:12个获取手机应用程序设计灵感的网站
  • 原文地址:https://www.cnblogs.com/littlehb/p/15111650.html
Copyright © 2011-2022 走看看