类似于食物链那道题,使用一个大小为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; }
做并查集的题目总是得要想着它内部的实现,因为它根本就没有接口,没有封装,STL没有它.