codevs2597-团伙-解题报告

题目描述 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;
}

这样这个问题就解决了。

    原文作者:犯罪团伙问题
    原文地址: https://blog.csdn.net/dengping_ss/article/details/50410387
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞