团伙(并查集)详解版

【问题描述】

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

输入格式

       第 1 行为 n 和 m,1   以下 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
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,那么下次遇到同一个团伙就会直接跳过了,这样可以达到统计数目的目的。

你可能感兴趣的:(算法,c++,c语言,数据结构)