[Baltic2003]gang团伙(解法一)

Problem : [Baltic2003]gang团伙

Time Limit: 1 Sec Memory Limit: 128 MB

Description

在某城市里住着n个人,任何两个认识的人不是朋友就是敌人,而且满足:
1、 我朋友的朋友是我的朋友;
2、 我敌人的敌人是我的朋友;
所有是朋友的人组成一个团伙。告诉你关于这n个人的m条信息,即某两个人是朋友,或者某两个人是敌人,请你编写一个程序,计算出这个城市最多可能有多少个团伙?

Input

第1行为n,第二行为m,N小于1000,M小于5000;
以下m行,每行为p x y,p的值为0或1,p为0时,表示x和y是朋友,p为1时,表示x和y是敌人。

Output

一个整数,表示这n个人最多可能有几个团伙。

Sample Input

6
4
E 1 4
F 3 5
F 4 6
E 1 2

Sample Output

3

HINT

{1},{2,4,6},{3,5}

题解:

1.题意分析

本题很清楚的告诉我们,两个人不是朋友,就是敌人,
而且每组数据都清楚地告诉你,哪两个数不能在同一集合中;
那么我们就能知道:
1.若a与b是敌人:
a与b不在同一集合中;
a必与b的敌人在同一集合中,b必与a的敌人在同一集合中;

2. 若a与b是好朋友:
a与b在同一集合中;
同时a,b的朋友都跟a和b在同一集合里。

好吧,的确很绕。
现在借助一下wym神犇的话来简洁地说明一下:
1、若x和y在同一棵树上,y和z在同一棵树上,那么x和z也在同一棵树上。
2、若x和y不在同一棵树,x和z也不在同一棵树上,那么y和z在同一棵树上(若题目指出两个数不在同一棵树上)

其实这个思路也是wym神犇传授于我的,非常感谢wym神犇,告诉了我大意。wym神犇关于这题的题解报告

那么,我们来想这一道题到底要我们求什么:

一个整数,表示这n个人最多可能有几个团伙。

很显然,它要求我们求出联通块的个数,说通俗点,就是:

n个人中有多少组朋友。[有多少个祖先]

若还不理解,就模拟下样例:
[F表示两人是朋友,E表示两人是敌人]

Sample Input

6
4
E 1 4
F 3 5
F 4 6
E 1 2

data 1:
E 1 4
1和4是敌人,具体情况如下(还未出现的数表明未操作到):
在这里插入图片描述
data 2:
F 3 5
3和5是朋友,具体情况如下:
在这里插入图片描述
data 3:
F 4 6
4和6是朋友,具体情况如下:
在这里插入图片描述
data 4:
E 1 2
1和2是敌人,记住此时1和4是敌人,那么2要和4所在的联通块合并,具体情况如下:
在这里插入图片描述
显然ans=3.
那么我们如何实现这个目的呢?
我们不难发现,它要我们求的团伙个数就是这n个数中有几个数的“祖先”是自己。
因为是不断的合并,所以我们自然的能想到并查集。

2.题目解答:

我们发现,其实主要关注的对象是朋友,那么敌人就可以当做操作中的过渡。将敌人转换为“他的敌人”,不就是我们所关注的朋友了吗?
具体方法如下:
1.若a,b是朋友,则直接将两人所在的集合合并;
2.反之,则a的敌人与b合并,要注意,b的敌人也要去与a合并。
(双向的)

代码如下:
#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;

int n,m,ans;
bool gan[1001]; //gan表示当前祖先是否已被访问,可要可不要
int fa[10001],foe[10001];
//fa表示当前节点父亲,foe表示当前节点的敌人

int find(int x)
{
	return x==fa[x]? fa[x]:fa[x]=find(fa[x]); //路径压缩,不再多讲
}

int main()
{
	char p;
	int x,y;
	scanf("%d\n%d",&n,&m);
	for (int i=1;i<=n;i++) fa[i]=i; //初始化
	for (int i=0;i<m;i++)
	{
		cin>>p;
		scanf("%d%d",&x,&y);
		int a=find(x),b=find(y); //找到a,b的祖先
		if (p=='F') //若为朋友
		{
			if (a!=b) fa[a]=b;
			//直接合并,注意判重,而且这个语句一定要单独拿出来,否则会WA,本蒟蒻也不知道为什么
		}
		else
		{
			if (!foe[x]) foe[x]=y; //注意若当前节点没有敌人,就将读入的数据进行赋值
			if (!foe[y]) foe[y]=x; //同上
			fa[a]=find(foe[y]); //将a与其敌人的敌人所在的集合合并
			fa[b]=find(foe[x]); //同上
			//双向搜索
		}
	}
	for (int i=1;i<=n;i++)
		if (!gan[find(i)]) //去寻找每个节点的祖先,若未被访问过,则说明又多了一个团伙
			gan[find(i)]=true,ans++; //将当前祖先打上标记,更新ans
	/*
		为了呼应刚才我说的“祖先是自己”的说法,或者也可以这么写:
		for (int i=1;i<=n;i++)
		if (fa[i]==i) //去寻找每个节点的祖先,若为自身,则说明又多了一个团伙
			ans++; //更新ans
	*/
	//可以少开一个逻辑数组,滑稽
	printf("%d",ans);
	return 0;
}
不当之处,望各位神犇及时指出!本蒟蒻会努力赶完另一种解法的。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值