信息学奥赛一本通 1385:团伙(group) | 洛谷 P1892 [BOI2003]团伙

该文章介绍了一道关于团伙关系的编程题,主要运用并查集数据结构处理朋友和敌人关系。通过并查集合并操作,当两个人是朋友时合并他们的集合,根据‘我敌人的敌人是我的朋友’规则处理敌人关系。最后统计集合数量输出结果。

【题目链接】

ybt 1385:团伙(group)
洛谷 P1892 [BOI2003]团伙
注:两题题面相同,解题逻辑相同,输入格式不同。

【题目考点】

1. 并查集
2. 扩展域并查集(种类并查集)

【解题思路】

解法1:设数组保存每个人的敌人

每个人是一个元素,一个团伙是一个集合。

  • 如果A与B是朋友,那么A与B同属于一个集合,应该将A所在的集合与B所在的集合合并。
  • 如果A与C是敌人,A与D是敌人,根据“我敌人的敌人是我的朋友”,A是C的敌人,D是C的敌人(A)的敌人,因此D与C是朋友。
    同理,A的所有敌人都是朋友,只需要知道A的一个敌人,通过并查集中的查询操作,就能够知道A的敌人组成的集合是什么。

也就是说一个人的所有敌人都互为朋友,同属于一个集合。

因此,设数组enemy, enemy[i]表示i的某一个敌人。
当x与y互为敌人时,先考虑y作为x的敌人的情况:

  • 如果x还没有敌人(enemy[x] == 0),就将y设为x的一个敌人,即enemy[x]=y
  • 如果x已有敌人(enemy[x] > 0),那么应该将y加入x的敌人组成的集合,即将y与enemy[x]所在的集合合并。

反过来,同时要考虑x作为y的敌人的情况:

  • 如果y还没有敌人(enemy[y] == 0),就将x设为y的一个敌人,即enemy[y]=x
  • 如果y已有敌人(enemy[y] > 0),那么应该将x加入y的敌人组成的集合,即将x与enemy[y]所在的集合合并。

并查集中每个集合的根结点满足fa[i] == i,集合的数量就是fa数组中根结点的个数。

最后输出集合的数量。

解法2:扩展域并查集

对每个人 i i i,假想存在 i i i这个人的一个敌人为 i + n i+n i+n
当前考虑 2 n 2n 2n个元素。将fa数组的长度扩展为2000。
并查集初始化时,要将 2 n 2n 2n个元素都进行初始化,将每个结点的双亲设为自己。
一个集合表示一个团伙

  • 如果 x x x y y y是朋友,则将 x x x y y y所在集合合并,执行merge(x, y)
  • 如果 x x x y y y是敌人,那么:
    • x + n x+n x+n与其敌人 x x x的敌人 y y y是朋友,执行merge(x+n,y)
    • y + n y+n y+n与其敌人 y y y的敌人 x x x是朋友,执行merge(x,y+n)

统计集合数量时,不能遍历fa数组下标范围 [ 1 , n ] [1,n] [1,n],通过判断fa[i] == i统计有几个结点为根结点。因为有可能存在集合的根结点地址在下标范围 [ n + 1 , 2 n ] [n+1,2n] [n+1,2n]
也不能遍历fa数组下标范围 [ 1 , 2 n ] [1,2n] [1,2n]通过判断fa[i] == i统计有几个结点为根结点。因为存在一些完全由下标范围在 [ n + 1 , n ] [n+1,n] [n+1,n]中的结点构成的集合。下标范围 [ n + 1 , n ] [n+1,n] [n+1,n]各结点是假想的,不是给定的结点,这些假想的结点构成的集合不能算作要统计的集合。

因此,需要遍历下标范围 [ 1 , n ] [1,n] [1,n],将每个结点所在集合的根结点地址求出,而后使用set去重,set集合中的元素数量就是不同的根结点的数量,也就是下标范围 [ 1 , n ] [1,n] [1,n]的各结点所在的集合的数量,即题目所求的团伙数量。

【题解代码】:ybt 1385:团伙(group)

解法1:设数组保存每个人的敌人

#include<bits/stdc++.h>
using namespace std;
#define N 1005
int n, m, fa[N], enemy[N];//fa[i]:i的双亲 enemy[i]:i的某个敌人 
void init(int n)
{
	for(int i = 1; i <= n; ++i)
		fa[i] = i;
}
int find(int x)//查找x所在集合的根结点
{
	if(fa[x] == x)
		return x;
	return fa[x] = find(fa[x]);
} 
void merge(int x, int y)//合并x,y所在的集合
{
	fa[find(x)] = find(y);	
} 
int main()
{
	int opt, p, q, cnt = 0;
	cin >> n >> m;
	init(n);
	for(int i = 1; i <= m; ++i)
	{
		cin >> opt >> p >> q;
		if(opt == 0)
			merge(p, q);
		else //opt = 'E' 敌人 
		{
			if(enemy[p])//如果p有敌人enemy[p] 
				merge(enemy[p], q);//将q加入到p敌人组成的集合 
			else//如果p没有敌人 
				enemy[p] = q;//q作为p的敌人 
			if(enemy[q])
				merge(enemy[q], p);
			else
				enemy[q] = p;
		}
	}
	for(int i = 1; i <= n; ++i) if(fa[i] == i)//i是根结点 
		cnt++;
	cout << cnt;
	return 0;
}

解法2:扩展域并查集

#include <bits/stdc++.h>
using namespace std;
#define N 2005
int fa[N];
void init(int n)
{
	for(int i = 1; i <= n; ++i)
		fa[i] = i;
}
int find(int x)
{
	return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void merge(int x, int y)
{
	fa[find(x)] = find(y);
}
int main()
{
	int opt, n, m, p, q, ans = 0;
	cin >> n >> m;
	init(2*n);
	for(int i = 1; i <= m; ++i)
	{
		cin >> opt >> p >> q;
		if(opt == 0)
			merge(p, q);
		else
		{
			merge(p+n, q);
			merge(p, q+n);
		}
	}
	set<int> st;
	for(int i = 1; i <= n; ++i)
		st.insert(find(i));
	cout << st.size();
	return 0;
}

【题解代码】:洛谷 P1892 [BOI2003]团伙

解法1:设数组保存每个人的敌人

#include<bits/stdc++.h>
using namespace std;
#define N 1005
int n, m, fa[N], enemy[N];//fa[i]:i的双亲 enemy[i]:i的某个敌人 
void init(int n)
{
	for(int i = 1; i <= n; ++i)
		fa[i] = i;
}
int find(int x)//查找x所在集合的根结点
{
	if(fa[x] == x)
		return x;
	return fa[x] = find(fa[x]);
} 
void merge(int x, int y)//合并x,y所在的集合
{
	fa[find(x)] = find(y);	
} 
int main()
{
	char opt;
	int p, q, cnt = 0;
	cin >> n >> m;
	init(n);
	for(int i = 1; i <= m; ++i)
	{
		cin >> opt >> p >> q;
		if(opt == 'F')
			merge(p, q);
		else //opt = 'E' 敌人 
		{
			if(enemy[p])//如果p有敌人enemy[p] 
				merge(enemy[p], q);//将q加入到p敌人组成的集合 
			else//如果p没有敌人 
				enemy[p] = q;//q作为p的敌人 
			if(enemy[q])
				merge(enemy[q], p);
			else
				enemy[q] = p;
		}
	}
	for(int i = 1; i <= n; ++i) if(fa[i] == i)//i是根结点 
		cnt++;
	cout << cnt;
	return 0;
}

解法2:扩展域并查集

#include <bits/stdc++.h>
using namespace std;
#define N 2005
int fa[N];
void init(int n)
{
	for(int i = 1; i <= n; ++i)
		fa[i] = i;
}
int find(int x)
{
	return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void merge(int x, int y)
{
	fa[find(x)] = find(y);
}
int main()
{
	int n, m, p, q, ans = 0;
	char opt;
	cin >> n >> m;
	init(2*n);
	for(int i = 1; i <= m; ++i)
	{
		cin >> opt >> p >> q;
		if(opt == 'F')
			merge(p, q);
		else
		{
			merge(p+n, q);
			merge(p, q+n);
		}
	}
	set<int> st;
	for(int i = 1; i <= n; ++i)
		st.insert(find(i));
	cout << st.size();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值