【题目链接】
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)
-
x
+
n
x+n
x+n与其敌人
x
x
x的敌人
y
y
y是朋友,执行
统计集合数量时,不能遍历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;
}
该文章介绍了一道关于团伙关系的编程题,主要运用并查集数据结构处理朋友和敌人关系。通过并查集合并操作,当两个人是朋友时合并他们的集合,根据‘我敌人的敌人是我的朋友’规则处理敌人关系。最后统计集合数量输出结果。

491

被折叠的 条评论
为什么被折叠?



