题目描述 Description
1920年的芝加哥,出现了一群强盗。如果两个强盗遇上了,那么他们要么是朋友,要么是敌人。而且有一点是肯定的,就是:
我朋友的朋友是我的朋友;
我敌人的敌人也是我的朋友。
两个强盗是同一团伙的条件是当且仅当他们是朋友。现在给你一些关于强盗们的信息,问你最多有多少个强盗团伙。
输入描述 Input Description
输入文件gangs.in的第一行是一个整数N(2<=N<=1000),表示强盗的个数(从1编号到N)。 第二行M(1<=M<=5000),表示关于强盗的信息条数。 以下M行,每行可能是F p q或是E p q(1<=p q<=N),F表示p和q是朋友,E表示p和q是敌人。输入数据保证不会产生信息的矛盾。
输出描述 Output Description
输出文件gangs.out只有一行,表示最大可能的团伙数。
样例输入 Sample Input
6
4
E 1 4
F 3 5
F 4 6
E 1 2
样例输出 Sample Output
3
数据范围及提示 Data Size & Hint
2<=N<=1000
1<=M<=5000
1<=p q<=N
分析与解:最近老在做并查集的题,今天老师又给我们一题。学校里一直得不出正确答案,在一个地方有一点小错误,其实也不算是什么错误,这个一会儿再提及。所以,忘了学校网站的网址了,于是只能上codevs去做。直面这道问题,问题是我们需要用并查集如何维护一个用于解决问题的集合。当时我记得班上一位同学是这样建模的:使用并查集维护朋友关系,而不维护敌人关系,因此,敌人关系不能直接体现而必须通过“敌人的敌人是朋友“这条公式来计算。具体的方法是遍历一个人的敌人列表,再读出每一个敌人的所有敌人,就得到了从”敌人的敌人“得来的朋友,然后用并查集维护。我觉得这个方法是很直接的,也很清楚,但我想出了一个实现更简单的方法实现如下:直接使用一个并查集,不仅维护朋友关系也维护敌人关系。当然,这不能简单的套并查集的模型,而要动一动脑筋。你想,如果1是2的敌人,3又是2的敌人,那么1就是3的朋友,想一想,这其实也很像并查集。为什么这么说呢?你看,把1,2,3给合并起来,去掉公共敌人,就是我们想得到的朋友。所以,仔细思考后,我是这样想的:使用2*n个元素来建立并查集,那么1~n就用来表示强盗们,n+1~2*n就用来表示他们的敌人,问题到这里来就已经很明晰了,只要a和b是敌人,那么我就把a和b+n给并起来,这样,只要有公共的敌人,那么在1~n中就会自动的连通,而敌人却隐身在n之后的元素中。就是这样,太棒啦!好吧,现在再说刚才那个小错误。我原以为既然友友为友,敌敌也为友,那么朋友的敌人也该是敌人,敌人的朋友也是敌人吧,不然太不仗义了吧!结果,题目还真是不仗义,结果敌人的朋友还真没规定就是敌人,同样的朋友的敌人也没规定是敌人,这让我觉得真是不合理,所以,我建议大家去思考一下,更加符合常理的情况下,又该怎么做。我之所以认为我之前想的不错,就是因为如果以这种方式思考,我认为之前那位同学的方法就变得更加复杂,而且容易出错,而我只需要加上一行代码。好了,大家可以看看我的代码。
#include <cstdio>
#define MAX_M 100000
#define MAX_N 1000
int n,m,ans;
int par[MAX_N*2+1];//注意,用2*n个元素来建立并查集
//你懂的,下面的标准并查集实现是从网络上采集并背下来的
void init(int c) {
for (int i=1;i<=c;i++) par[i]=i;
}
int find(int a) {
if (par[a]==a) return a;
int t=a;
while (par[a]!=a) {
a=par[a];
}
while (par[t]!=t) {
int t2=t;
t=par[t];
par[t2]=a;
}
return a;
}
inline void unite(int a,int b) {
a=find(a);
b=find(b);
if (a!=b) {
par[b]=a;
}
}
inline bool same(int a,int b) {
return find(a)==find(b);
}
//到这里都是并查集的实现
int main() {
scanf("%d%d",&n,&m);
init(n*2);
for (int i=1;i<=m;i++) {
int x,y;
char c[2];//没办法,字符读不进来,只能猥琐一点读字符串了,谁让我忘了学校的网址呢
scanf("%s",c);
scanf("%d%d",&x,&y);
if (c[0]=='F') {
unite(x,y);//注意,这里是把朋友并起来
//分析刚刚说到的那种情况,敌人的朋友是敌人,朋友的敌人也是敌人该怎么加代码,很简单,一句话:unite(x+n,y+n);
} else {
unite(x,y+n);//这里是合并敌人,注意,敌人是互相的,不能只并一个,必须保证敌人不会丢失掉
unite(y,x+n);
}
}
int ans=0;
for (int i=1;i<=n;i++) {//今天才学到的数集合个数的代码
if (find(i)==i) ans++;
}
printf("%d\n",ans);
return 0;
}
这样这个问题就解决了。