并查集经典例题与二分图的应用

题目如下:

题目链接:P1525 关押罪犯问题

题目描述

S 城现有两座监狱,一共关押着 N 名罪犯,编号分别为 1−N。他们之间的关系自然也极不和谐。很多罪犯之间甚至积怨已久,如果客观条件具备则随时可能爆发冲突。我们用“怨气值”(一个正整数值)来表示某两名罪犯之间的仇恨程度,怨气值越大,则这两名罪犯之间的积怨越多。如果两名怨气值为 c 的罪犯被关押在同一监狱,他们俩之间会发生摩擦,并造成影响力为 c 的冲突事件。

每年年末,警察局会将本年内监狱中的所有冲突事件按影响力从大到小排成一个列表,然后上报到 S 城 Z 市长那里。公务繁忙的 Z 市长只会去看列表中的第一个事件的影响力,如果影响很坏,他就会考虑撤换警察局长。

在详细考察了N 名罪犯间的矛盾关系后,警察局长觉得压力巨大。他准备将罪犯们在两座监狱内重新分配,以求产生的冲突事件影响力都较小,从而保住自己的乌纱帽。假设只要处于同一监狱内的某两个罪犯间有仇恨,那么他们一定会在每年的某个时候发生摩擦。

那么,应如何分配罪犯,才能使 Z 市长看到的那个冲突事件的影响力最小?这个最小值是多少?

输入格式

每行中两个数之间用一个空格隔开。第一行为两个正整数 N,M,分别表示罪犯的数目以及存在仇恨的罪犯对数。接下来的 MM 行每行为三个正整数 aj​,bj​,cj​,表示 aj​ 号和 bj​ 号罪犯之间存在仇恨,其怨气值为 cj​。数据保证 1<aj,bj< N, 0 < cj< 10^9   1<aj​≤bj​≤N,0<cj​≤109,且每对罪犯组合只出现一次。

输出格式

共 11 行,为 Z 市长看到的那个冲突事件的影响力。如果本年内监狱中未发生任何冲突事件,请输出 0

输入输出样例

输入 #1

4 6
1 4 2534
2 3 3512
1 2 28351
1 3 6618
2 4 1805
3 4 12884

输出 #1

3512

说明/提示

【输入输出样例说明】罪犯之间的怨气值如下面左图所示,右图所示为罪犯的分配方法,市长看到的冲突事件影响力是 35123512(由 22 号和 33 号罪犯引发)。其他任何分法都不会比这个分法更优。

【数据范围】

对于30%的数据有 N≤15。

对于70% 的数据有 N≤2000,M≤50000。

对于100% 的数据有 N≤20000,M≤100000。

 



并查集解决

这题用并查集真的不好想,容易受到思维定势的影响(省选题真的不简单!!)

有关并查集,前面的博客有介绍

思路如下:

首先注意到只有两个监狱,如何让最大的冲突值最小呢?

显然是让冲突值最大的罪犯分开存放!----->由此想到先对冲突值从大到小排序,依次放到两个监狱中

 

接下来考虑:如何用并查集解决每个罪犯的安放位置,即把所有罪犯分成两个部分!!!

当冲突关系按照从大到小排过序之后,

下面存放第一个最大冲突值最大的对象,很简单。

下面存放第二大冲突值的对象:

 考虑如下情况,将要存放的一个对象已经被安防过了,此时我们必须把另一个对象放到对立面。(用一个数组保存每一个的罪犯对手)因此在上面第一步的时候就必须把刚放进去的两个罪犯的各自的对手存起来,并且把他们分别作为两个监狱的father(即并查集的根节点)

如果没有对象存在的话,直接保存各自的对手,并把两个罪犯分别插入进监狱中(此时的插入只能简单的存放到father数组中还无法做到与两个father相关联,想做到关联还需要继续往下看)

下面接着存放下面的元素:

可能遇到的情况还是第二大冲突值的情况,照旧即可。

此时还有可能出现如下情况:

如果已经存在a,b是对手的关系,下面要添加a,c应该如何做呢?对手的对手就是朋友,bc是一伙的,这就解决了上面无法合并对手关系的问题。具体做法就是union(b,c)具体见代码即可

将要添加的关系中,两个元素已经被上一种情况划分到同一个father中了,应该怎么办呢?

我们知道之前做的每一步都是将有矛盾的两个人分开,且是按照矛盾值从大到小分隔开!!显然这种情况就是两个人的冲突无法避免,如果要让现在的两个人矛盾化解,就必须合并之间存放好的罪犯,显然合并后冲突值一定大于当前值。故此情况就是结束的标志。

至此,按并查集和贪心的想法分析结束,下面是AC代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int MAX = 100005;

int M,N;
int father[MAX];
int b[MAX]; //存放每个对手
struct Rel{
    int s,d,v;
}rel[MAX]; //关系

bool cmp(Rel aa,Rel bb) {
    return aa.v>bb.v;
}

//模板
int getFather(int x) {
    if(father[x]==x) return x;
    else return father[x]=getFather(father[x]);
}

//模板
void unionChild(int aa,int bb){
    int fatherA = getFather(aa);
    int fatherB = getFather(bb);
    if(fatherA!=fatherB) father[fatherA]=fatherB;
}

int main() {
    cin >> N >> M;
    //并查集初始化
    for (int i = 1; i <= N; ++i) {
        father[i]=i;
    }
    for (int i = 0; i < M; ++i) {
        int s,d,x;
        cin >> s >> d >>x ;
        rel[i].s = s;
        rel[i].d = d;
        rel[i].v = x;
    }
    sort(rel,rel+M,cmp);
    for (int i = 0; i <= M; ++i) {
        //如果到最后关系也不会冲突,就表示没有冲突,直接打印0
        if(i==M) {
            cout << 0 << endl;
            return 0;
        }
        int f1 = getFather(rel[i].s);
        int f2 = getFather(rel[i].d);
        if(f1==f2) {        //如果两者已经存在同一个father中,表示结束
            cout << rel[i].v << endl;
            return 0;
        }
           
        //存放每个对象的对手,因为可能存在多个对手,就需要把对手放进另一个father中,即对手们
        if(b[rel[i].s]){
            unionChild(b[rel[i].s],f2);
        } else b[rel[i].s]=rel[i].d;
        //同上
        if(b[rel[i].d]) {
            unionChild(b[rel[i].d],f1);
        } else b[rel[i].d]=rel[i].s;

    }
}

类似题:

P1892 [BOI2003]团伙 比这题简单

下面用二分图解决

其实看到求最小的最大值,第一反应就是二分查找!!!

但我现在并不会二分图相关知识,先补二分图的知识咯

如果一张无向图的n个节点(n>=2)可以分为A,B两个集合,且满足A ∩ B = ∅ ,而且在同一集合内的点之间都没有边相连,那么这张无向图被称为二分图,其中A和B分别叫做二分图的左部和右部。

二分图判定定理

一张无向图是二分图: 当且仅当图中不存在奇环(奇环是指长度为奇数的环)

#include<bits/stdc++.h>
#define N 20009
using namespace std;
int n,m,a,b,c,x,BiG,vst[N];
vector<int> to[N],w[N];
void dfs(int u,int color){//u将染色color 
    if(!BiG)return;
    vst[u]=color;
    for(int i=0;i<to[u].size();i++)
        if(w[u][i]>x){
            if(!vst[to[u][i]])dfs(to[u][i],3-color);
            else if(vst[to[u][i]]==color)BiG=0; //邻居染色相同为非法 
        }
}
bool OK(int mid){//能否不看到mid以上冲突,大边需要进二分图 
    x=mid;  BiG=1;
    fill(vst,vst+n+1,0);
    for(int i=1;i<=n&&BiG;i++)
        if(!vst[i]) dfs(i,1);
    return BiG;
}
int main(){
    cin>>n>>m;
    int l=0,r=0;
    for(int i=0;i<m;i++){
        cin>>a>>b>>c;
        to[a].push_back(b);w[a].push_back(c);
        to[b].push_back(a);w[b].push_back(c);
        r=max(r,c);
    }
    int ans=r;
    while(l<=r){
        int mid=l+(r-l)/2;
        if(OK(mid))ans=mid,r=mid-1;
        else l=mid+1;
    }
    cout<<ans<<endl;    
    return 0;
}

 

先挖个坑,,,以后熟练了再补二分图的解法。哈哈哈哈

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章