并查集

并查集

这种数据结构用来表示集合信息,实现如确定某个集合含有哪些元素、判断某两个元素是否存在同一个集合中、求集合中元素的数量等问题。
也就是用一个树来表示集合关系。
在这里插入图片描述
在这里插入图片描述
图片内容摘自《王道》。
并查集一般有几个基本操作:

  • 寻找结点的根,在过程中进行路径压缩
  • 合并两个集合
  • 判断是不是同一集合

我们一般用一个数组表示下标对应结点及其根节点的关系。

const int maxn = 5000;
int Tree[maxn];
void init(int  n){    //初始化每个结点的根节点是自己
   for(int i = 0; i <= n;++i){
       Tree[i] = i;
   }
}

//寻找根节点
int findRoot(int x){     
   if(Tree[x] != x)
       Tree[x] = findRoot(Tree[x]);    //路径压缩
   return Tree[x];
}

//合并两个集合
void unite(int x,int y){
   int fx = Tree[x];
   int fy = Tree[y];
   if(fx != fy){
           Tree[fx] = fy;
   }
   return;
}

bool  same(int x,int y){
   return findRoot(x) == findRoot(y);
}

在此基础上,为了在合并结点时防止数的退化,我们还可以加入按秩合并的方法,也就是用一个数组保存树深,把较矮的树合并到较高的树上去。

void unite(int x,int y){
    int fx = Tree[x];
    int fy = Tree[y];
    if(fx != fy){
    		if(deep[fx] < deep[fy])   //deep数组保存树深,合并矮树
            	Tree[fx] = fy;
            else{
            	Tree[fy] = fx;
            	if(deep[fx] == deep[fy]) 
            		deep[fx]++;
            }
    }
    return;
}

简单的并查集可以解决无向图的最小生成树Krusal算法,给节点排序后不断的将新节点合并进来,在合并的过程中计算权值和,直到所有结点都在同一集合。
同样的思想,可以解决至少加几条路才能使一个无向图强连通,计算有几个集合,道路条数等于集合数减一。


种类并查集

下面我们来聊聊种类并查集,以一个例题开始:
POJ 2942 寻找GAY虫


Description

Background
Professor Hopper is researching the sexual behavior of a rare species of bugs. He assumes that they feature two different genders and that they only interact with bugs of the opposite gender. In his experiment, individual bugs and their interactions were easy to identify, because numbers were printed on their backs.
Problem
Given a list of bug interactions, decide whether the experiment supports his assumption of two genders with no homosexual bugs or if it contains some bug interactions that falsify it.

Input

The first line of the input contains the number of scenarios. Each scenario starts with one line giving the number of bugs (at least one, and up to 2000) and the number of interactions (up to 1000000) separated by a single space. In the following lines, each interaction is given in the form of two distinct bug numbers separated by a single space. Bugs are numbered consecutively starting from one.

Output

The output for every scenario is a line containing "Scenario #i:", where i is the number of the scenario starting at 1, followed by one line saying either "No suspicious bugs found!" if the experiment is consistent with his assumption about the bugs' sexual behavior, or "Suspicious bugs found!" if Professor Hopper's assumption is definitely wrong.

Sample Input

2
3 3
1 2
2 3
1 3
4 2
1 2
3 4

Sample Output

Scenario #1:
Suspicious bugs found!
Scenario #2:
No suspicious bugs found!

Hint

Huge input,scanf is recommended.


题目大意就是给出虫子的依恋关系,一般来说应该是异性恋,但是有一种特殊的虫子是同性恋,让你通过给出的关系找找有米有GAY虫。
很明显我们可以定义出两个集合为两个性别,如果我们发现有依恋关系的两个虫子属于同一个集合,那么就发现了GAY虫。思路上是不难理解的。
在代码方面,我们把一个数组分成两个部分,前半部分存放雄性,后半部分存放雌性。也就是说我们会定义一个大小为 2 n 2n 2n的数组。
先给出代码:

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int maxn = 5000;
int par[maxn];
void init(int n){
   for(int i=0;i<=n;i++)
      par[i]=i;
}
int find(int x){
    if(x!=par[x])
        par[x]=find(par[x]);
    return par[x];
}
void Union(int a,int b){
    int x=find(a);
    int y=find(b);
    if(x!=y)
       par[x]=y;
}
bool judge(int x,int y){//判断是否为同性,异性为真,同性为假
   x=find(x);
   y=find(y);
   if(x!=y)
    return true;
   return false;
}
int main()
{
    int t,n,m,cas=1;
    scanf("%d",&t);
    for(int i=1;i<=t;i++){
        scanf("%d%d",&n,&m);
        init(n*2);
        bool l=1;
        int x,y;
        while(m--){
            scanf("%d%d",&x,&y);
            if(judge(x,y)||judge(x+n,y+n)){
                Union(x,y+n);//把同性的加到一个集合里
                Union(x+n,y);
            }
            else
                l=0;
        }
        if(i!=1)
            puts("");
        printf("Scenario #%d:\n",cas++);
        if(l)
            printf("No suspicious bugs found!\n");
        else
            printf("Suspicious bugs found!\n");
    }
    return 0;
}

大家可能会对这一段代码产生迷惑:

先聊聊一个简单的推理:有虫子ABC,A love B ,能推理出A和B应当互为异性,现在又知道了B love C,能推理出B和C互为异性,还能推理出A 和 C为同性。
在这里插入图片描述
在这里插入图片描述
观察一下每个虫子的同性和异性集合,发现具有依恋关系的两个虫子AB,A的同性集是B的异性集,所以在有新的关系产生的时候,就要去更新各自的同异性集合。
当然,当我们发现这样的情况时:
在这里插入图片描述
毫无疑问,这三只虫子中一定有一个是GAY!不信的话,可以比较一下同异性集合XD
(以上图中应该都是无向边)


下面这道题相对难一些啦,但本质上也是种类并查集的问题,只不过这里会出现三个集合:同类,吃,被吃。
POJ 1182 食物链

Description

动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B, B吃C,C吃A。
现有N个动物,以1-N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。
有人用两种说法对这N个动物所构成的食物链关系进行描述:
第一种说法是"1 X Y",表示X和Y是同类。
第二种说法是"2 X Y",表示X吃Y。
此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
1) 当前的话与前面的某些真的话冲突,就是假话;
2) 当前的话中X或Y比N大,就是假话;
3) 当前的话表示X吃X,就是假话。
你的任务是根据给定的N(1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。

Input

第一行是两个整数N和K,以一个空格分隔。
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。
若D=1,则表示X和Y是同类。
若D=2,则表示X吃Y。

Output

只有一个整数,表示假话的数目。

Sample Input

100 7
1 101 1 
2 1 2
2 2 3 
2 3 3 
1 1 3 
2 3 1 
1 5 5

Sample Output

3

附上代码:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;int n,k;
const int MAXN =  100005;
int Tree[3*MAXN];
int findRoot(int x){
    if(Tree[x] == x)return x;
    int tmp = findRoot(Tree[x]);
    Tree[x] = tmp;
    return tmp;
}

void merge(int x,int y){
    int a = findRoot(x);
    int b = findRoot(y);
    if(a != b){
        Tree[a] = b;
    }
}

bool same(int x,int y){
    return findRoot(x) == findRoot(y);
}

int main(){

     scanf("%d%d", &n, &k);
         for(int i = 0; i < n * 3;++i) Tree[i] = i;
          int ans = 0;
    for(int i = 0; i < k;++i)
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        int tag = a;
        int x = b - 1;
        int y = c - 1;
         if(x < 0 || x >= n || y < 0 || y >= n)
        {
            ans++;
            continue;
        }
        if(tag == 1){
            if(same(x,y+n) || same(x ,y+ 2 * n)){
                ans++;

            }
            else{
                merge(x,y);
                merge(x+n,y+n);
                merge(x+2*n,y+2*n);
            }

        }
        else{
            if(same(x,y) || same(x,y+2*n)){
                ans++;

            }
            else{
                merge(x,y+n);
                merge(x+n,y+2*n);
                merge(x+2*n,y);
            }
        }
    }
    printf("%d\n",ans);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值