【问题描述】
在某城市里住着 n 个人,任何两个认识的人不是朋友就是敌人,而且满足: 1、我朋友的朋友是我的朋友; 2、我敌人的敌人是我的朋友; 所有是朋友的人组成一个团伙。告诉你关于这 n 个人的 m 条信息,即某两个人是朋友, 或者某两个人是敌人,请你编写一个程序,计算出这个城市多可能有多少个团伙?
【输入格式】
第 1 行为 n 和 m,1<n<1000,1<=m<=100 000;
以下 m 行,每行为 p x y,p 的值为 0 或 1,p 为 0 时,表示 x 和 y 是朋友,p 为 1 时, 表示 x 和 y 是敌人。
【输出格式】
一个整数,表示这 n 个人多可能有几个团伙。
【输入样例】
6 4
1 1 4
0 3 5
0 4 6
1 1 2
【输出样例】
3
这道题很明显的需要使用并查集,但又比并查集复杂一些。里面有两句话比较关键。(不会并查集的建议先去学一下)
我朋友的朋友是我的朋友
这句比较好理解,只要将根节点相同的归为一类就可以了,比较复杂的是下一句。
我敌人的敌人是我的朋友
这句话就比较难理解了,这个敌人要怎么在代码中表示出来呢?
我们可以在初始化时,可以初始化所需要的人数的二倍。比如说需要n个人,我们可以初始化2n个人。多出来的n个人是什么呢?就是每个人所对应的敌人节点啦。
举个栗子:
有两个人,为x和y,他们互为敌对关系,同时我们可以得到x的敌人除y还有x+n,y的敌人除x还有y+n,又因为敌人的敌人是我的朋友,所以x和y+n是朋友,y和x+n是朋友。
看到这里,各位对这道题是不是很明了呢
上代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
int p[15000];
int vis[15000];
int findth(int x)
{
if(x==p[x]) return x;
return p[x]=findth(p[x]);
}
void unionn(int x,int y)
{
int xx=findth(x);
int yy=findth(y);
if(xx!=yy) p[yy]=xx;
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=2*n;i++) p[i]=i;
for(int i=1;i<=m;i++){
int a,b,c;
scanf("%d %d %d",&a,&b,&c);
if(a==0) unionn(b,c);
if(a==1){
unionn(b+n,c);
unionn(b,c+n);
}
}
int cnt=0;
for(int i=1;i<=n;i++){
int t=findth(i);
if(!vis[t]){
vis[t]=1;
cnt++;
}
}
printf("%d\n",cnt);
return 0;
}
还有vis数组是怎么统计团伙数量的呢,原理是每一个团伙都只有一个根节点,所以将已经被遍历过的根节点赋值为1,那么下次遇到同一个团伙就会直接跳过了,这样可以达到统计数目的目的。