题目链接:http://poj.org/problem?id=1182
在挑战程序设计竞赛上看到的,方法很巧妙。
由于N和K很大,所以必须高效地维护动物之间的关系,并快速判断是否产生了矛盾。并查集是维护“属于同一组” 的数据结构,但是在本题中,并不只有属于同一类的信息,还有捕食关系的存在。因此需要开动脑筋维护这些关系。
对于每只动物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。因此,对于每一条信息,只需要按照下面进行操作就可以了。
1)第一种,x和y属于同一种类———合并x-A和y-A、x-B和y-B、x-C和y-C。
2)第二种,x吃y—————————合并x-A和y-B、x-B和y-C、x-C和y-A。
不过在合并之前需要先判断合并是否会产生矛盾。例如在第一种信息的情况下,需要检查比如x-A和y-B或者y-C是否在同一组等信息。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAX_N = 3 * 50100;
const int MAX_K = 100100;
int f[MAX_N];
int h[MAX_N]; //记录树的高度避免退化
int T[MAX_K], X[MAX_K], Y[MAX_K];
void init(int n) {
int i;
for(i = 0; i < n; i++) {
f[i] = i;
h[i] = 0;
}
}
int find(int x) {
if(f[x] == x) return x;
else return f[x] = find(f[x]);
}
void unite(int x, int y) {
x = find(x);
y = find(y);
if(x != y) {
if(h[x] < h[y]) f[x] = y;
else {
f[y] = x;
if(h[x] == h[y]) h[x]++; //高度变化
}
}
}
bool same(int x, int y) {
return find(x) == find(y);
}
int main() {
int n, k;
scanf("%d %d", &n, &k);
int i, j;
init(n * 3);
int x, y, z;
for(i = 0; i < k; i++) {
scanf("%d %d %d", T + i, X + i, Y + i);
}
int ans = 0;
for(i = 0; i < k; i++) {
int t = T[i];
int x = X[i] - 1, y = Y[i] - 1;
if(x < 0 || x >= n || y < 0 || y >= n) {
ans++;
continue;
}
if(t == 1) {
//需要判断以下6种情况,都是有捕食关系的,但是发现建立捕食关系时
//都是3个为一组建立的,所以只需要判断2种情况就可包括6种了
//same(x, y + n) || same(x, y + 2 * n) ||same(x + n, y) ||
//same(x + n, y + 2 * n) || same(x + 2 * n, y) || same(x + 2 * n, y + n)
if(same(x, y + n) || same(x, y + 2 * n)) {
ans++;
}
else {
unite(x, y);
unite(x + n, y + n);
unite(x + 2 *n, y + 2 * n);
}
}
else {
//同上也可把以下6种情况简化为2种
//same(x, y) || same(x, y + 2 * n) || same(x + n, y + n) ||
//same(x + 2 * n, y + 2 * n) || same(x + n, y) || same(x + 2 * n)
if(same(x, y) || same(x, y + 2 * n)) {
ans++;
}
else {
unite(x, y + n);
unite(x + n, y + 2 * n);
unite(x + 2 * n, y);
}
}
}
printf("%d\n", ans);
return 0;
}