种类并查集(关押犯人)

emmm又是一个很高大上的概念

很久前就接触过,可惜直到现在还是摸棱两可。

核心概念就是:朋友的朋友是我的朋友

       敌人的敌人是我的敌人

 

 

常见的做法是将原并查集扩大一倍规模,并划分为两个种类。

 

在同个种类的并查集中合并,和原始的并查集没什么区别,仍然表达他们是朋友这个含义。

 

考虑在不同种类的并查集中合并的意义,其实就表达 他们是敌人 这个含义了。

 

按照并查集美妙的 传递性,我们就能具体知道某两个元素到底是 敌人 还是 朋友 了。

如果a和b是敌人,合并n+b和a,n+a和b

如果c和a是敌人,合并n+c和a,n+a和c

那么b和c就并在一起了

因为a成为了他们的纽带

 

 

 那么,我们来看一下这道题目。关押犯人https://www.luogu.com.cn/problem/P1525

题面:n个犯人,m对关系,表示犯人A和犯人B的冲突值。

现在要把犯人分成两类,要求在同一个监狱中犯人的最大冲突值最小。

可以注意到,要减小冲突值,首先我们不能把冲突最大的分到一起。

 

 

 比如,我们知道1和2肯定在不同的监狱,因为他们的冲突值太大了。

然后我们再考虑,在这个基础上,4和3也最好也在不同的监狱

同理,1和3也是这样,2和3,以此类推

但是我们不能无限这样做下去,因为我们不能保证都在不同的监狱。

我们这么想:1和2是敌人

      4和3也是敌人

      1和3是敌人

      2和3是敌人

是不是发现,敌人的敌人就是我的朋友.

然后我们枚举到2和3的时候,发现他们是朋友

不可避免地,2和3冲突了。

#include <bits/stdc++.h>
using namespace std;
int pre[40009],n,m,l,r;
struct p{
    int l,r,v;
    bool operator < (const p&tmp)    const{
        return v>tmp.v;
    }
}a[100009];
int find(int x){
    if(x!=pre[x])    pre[x]=find(pre[x]);
    return pre[x];
}
void join(int q,int w){
    pre[find(q)]=find(w);
    return;
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=m;i++)    cin>>a[i].l>>a[i].r>>a[i].v;
    sort(a+1,a+1+m);
    for(int i=1;i<=n+n;i++)    pre[i]=i;
    for(int i=1;i<=m;i++)
    {
        int l=a[i].l,r=a[i].r;
        if(find(l)==find(r))
        {
            cout<<a[i].v;
            return 0;
        }
        join(l+n,r);
        join(r+n,l);
    }
    cout<<0;
}
View Code

 

 


 

posted @ 2020-03-11 16:11  倾叶子佮  阅读(263)  评论(2编辑  收藏  举报