P1892 [BOI2003]团伙

 

类似于食物链那道题,使用一个大小为2*n的并查集,对于第 i (1<=i<=n)个人,其朋友域是get(i),敌人域是get(i+n).

若x与y是朋友,则merge(x,y).题意并没有说明朋友的敌人是敌人,所以不需要合并x,y的敌人域.

若x与y是敌人,即x进入y的敌人域,y进入x的敌人域,merge(x, y + n), merge(y, x + n).

重点强调这里的merge要写成如下形式:

void merge(int x, int y) { fa[get(y)] = fa[get(x)]; }

即把y"挂"在x上.

既然集合内元素都是具有等价关系的,为什么反过来不可以呢?因为这道题目涉及到统计集合数量的问题.在处理完所有的合并操作后需要给出以1~n编号的人总共组成了多少个集合,如果把第 i 个人"挂"到了j + n上面,实践表明这和正确做法产生的总集合数量是相等的,但是我没有想到此时统计结果的有效方法.

至此,还需要处理"敌人的敌人是朋友",实际上已经不需要额外处理了,在执行敌人合并时:

假设有a,b,c三人,已知a与c敌对,b与c敌对,那么处理a,c的关系时,把c+n挂到a上,把a+n挂到c上:(一列表示一个集合)

   a     b     c     a+n     b+n     c+n

c+n         a+n

现在处理b,c的关系,把c+n(所在的集合)挂到b上,把b+n挂到c上:

   a     b     c     a+n     b+n     c+n

          a    a+n

   c+n  b+n

会发现a和b自动合并到了一起,他们现在是朋友关系了.

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;

int fa[2010], n, m;

int get(int x) {
    if (fa[x] == x) return x;
    return fa[x] = get(fa[x]);
}
void merge(int x, int y) { fa[get(y)] = fa[get(x)]; }

int main() {
    cin >> n >> m;
    for (int i = 1; i <= 2 * n; i++) fa[i] = i;

    while (m--) {
        char ch;
        int x, y;
        cin >> ch >> x >> y;
        if (ch == 'F')
            merge(x, y);
        else
            merge(x, y + n), merge(y, x + n);
    }

    int ans = 0;
    for (int i = 1; i <= n; i++)
        if (fa[i] == i) ans++;
    cout << ans << endl;

    return 0;
}
P1892

做并查集的题目总是得要想着它内部的实现,因为它根本就没有接口,没有封装,STL没有它.

posted @ 2021-02-05 09:49  goverclock  阅读(57)  评论(0编辑  收藏  举报