Poj1182 食物链(并查集/带权并查集)

题面

Poj

题解

这里采用并查集的补集。

$x$表示同类集合,$x+n$表示敌人集合,$x+n\times2$表示敌人的敌人集合。

如果当前给出的是一对同类关系,就判断$x$是否吃$y$或者$y$是否吃$x$(冲突)。

如果是真话,就将所有关系连在一起。

反之,如果给出的是一对$x$吃$y$关系,就判断$x$是否吃了自己(矛盾)或者他们两个互相吃(冲突)。

如果是真话,将$x$的敌人和$y$的敌人的敌人(朋友)连在一起,将$y$的敌人和$x$的敌人的敌人(朋友)连在一起,即$y$的敌人是$x$的朋友,将$y$丢进$x$的敌人集合中。

上面这对话可能有点绕口,但是大概还是讲出来了,仔细理解一下吧…

这里还有一个带权并查集的做法,但是并查集补集更好写(写起来更顺些)。

#include <cstdio>
#include <cstring>
#define Rg register 

const int N = 5e4 + 10;
int n, k, fa[N * 3], cz, x, y, ans, nn;

int find (int x) {
    if (fa[x] != x) fa[x] = find (fa[x]);
    return fa[x];
}

int unionn (int u, int v) {
    int fu = find (u), fv = find (v);
    fa[fv] = fu;
}//v -> u

int main () {
    scanf ("%d%d", &n, &k);
    nn = n * 3;
    for (Rg int i = 1; i <= nn; ++i)
        fa[i] = i;
    while (k--) {
        scanf ("%d%d%d", &cz, &x, &y);
        if (x > n || y > n) {++ans; continue;}//依据2
        if (cz == 1) {
            if (find (x + n) == find(y) || find (y + n) == find (x)) {++ans; continue;}//依据1
            unionn (x, y); unionn (x + n, y + n); unionn (x + (n << 1), y + (n << 1));
        } else {
            if (find (x) == find (y) || find (x) == find (y + n)) {++ans; continue;}//依据1&依据3
            unionn (y + (n << 1), x); unionn (y, x + n); unionn (y + n, x + (n << 1));
        }
    }
    printf ("%d\n", ans);
    return 0;
}