並查集經典例題與二分圖的應用

題目如下:

題目鏈接: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;
}

 

先挖個坑,,,以後熟練了再補二分圖的解法。哈哈哈哈

 

 

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