天天看點

二分比對算法學習篇水題集

目錄

  • 學習篇
    • 二分圖以及其最大比對
    • 匈牙利算法
    • 二分圖最大比對變形題
      • 最小頂點覆寫 || 最大獨立集
      • DAG圖的最小路徑覆寫
  • 水題集
    • hdu 2063:男女最大比對
    • hdu 1150:機器最少重新開機次數
    • hdu 1151:DAG最小覆寫路
    • hdu 1068:最大沒有聯系的男女個數
    • hdu 1281 :騎士最大放置數

學習篇

二分圖以及其最大比對

一、什麼是二分圖

如果一個圖的頂點可以分為兩個集合X、Y, 圖的所有邊一定是有一個頂點屬于集合X,另一個頂點屬于集合Y, 則稱該圖為“二分圖”或“二部圖”。

二、二分圖的最大比對

在二分圖的應用中,最常見的就是求————最大比對,很多其他問題都可以轉化為比對問題來解決。

匈牙利算法

舉個例子:男女婚配(詳看hdu 2063

二分比對算法學習篇水題集

周遊左邊男的:

男1:周遊右邊女的, 從 女1 開始,有連線并且本輪沒用過,則男1 女1 比對 為一對

男2:周遊右邊女的,從 女1 開始,有連線并且本輪沒用過,但女1 有對象,則看看 其對象是否有另外的選擇,若有則 到目前為止一共比對兩對,若無則 隻有一對

二分圖最大比對變形題

最小頂點覆寫 || 最大獨立集

定義:用最少的頂點覆寫所有的邊

最小頂點覆寫=比對對數(詳看hdu 1150)

最大獨立集=總頂點數-比對數

DAG圖的最小路徑覆寫

定義:用最少的路覆寫所有的點(詳看hdu 1151

最小路覆寫=點的總數 - 最大比對

因為:比對一個,點就少一個

做法:X、Y集合均為相同的頂點,表示從X -> Y的頂點

水題集

hdu 2063:男女最大比對

hdu 2063

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

int K,M,N,v[510],mp[510][510],p[510];
//mp[][]記錄兩頂點之間的連線,p用來儲存女生的對象,v标記數組

bool dfs(int i){//dfs的含義是 判斷該編号i的男生能否找到對象

    for(int j=1;j<=N;j++){//周遊女生
        if(mp[i][j]&&!v[j]){//若本輪有連線且沒用過的女生,則标記
            v[j]=1;
            if(!p[j] || dfs(p[j])){//若該女生沒有對象 或者 本來有對象但原來的對象能找到别的女生則可以讓 i編号的男生為 j編号的女生的對象
                p[j]=i;return 1;
            }
        }
    }
    return 0;//該男生沒找到對象
}


int main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    while(cin>>K){
        if(K==0)break;
        cin>>M>>N;
        int ans=0;
        memset(mp,0,sizeof mp);
        for(int i=1;i<=K;i++){
            int g,b;cin>>g>>b;
            mp[g][b]=1;
        }

        memset(p,0,sizeof(p));//将所有女生的對象初始化為一個值,且該值不等于任何男生的編号(-1或0)
        for(int i=1;i<=M;i++){
            memset(v,0,sizeof v);//每輪都要初始化
            if(dfs(i))ans++;//找到對象,比對數加加
        }
        cout<<ans<<endl;;
    }
    return 0;
}
           

hdu 1150:機器最少重新開機次數

hdu 1150

題意:兩個機器,初始模式均為0,變成下一個模式要重新開機,每個任務對于兩種機器有各自的模式,求兩種機器最少重新開機幾次才能完成所有任務

本質:最少頂點覆寫,邊的總數就是任務數,對應的兩種機器的模式就是兩個集合的點;

注意:機器本身就在0模式,是以要預處理一下:跟 0模式相連的點不需要 記錄兩點的連線

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

int n,m,k,v[110],mp[110][110],p[110];


bool dfs(int i){
    for(int j=1;j<m;j++){
        if(mp[i][j] && !v[j]){
            v[j]=1;
            if(p[j]==-1 || dfs(p[j]) ){
                p[j]=i;return 1;
            }
        }
    }
    return 0;
}

int main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    while(cin>>n){
        if(n==0)break;
        cin>>m>>k;

        memset(mp, 0,sizeof mp);
        for(int i=0;i<k;i++){
            int a,b;cin>>i>>a>>b;
            if(a==0 ||b==0)continue;//預處理
            mp[a][b]=1;
        }
        int ans=0;
        memset(p,-1,sizeof p);//因為“對象”有編号為0的,是以初始化為-1
        for(int i=1;i<n;i++){
            memset(v,0,sizeof v);
            if(dfs(i))ans++;
        }
        cout<<ans<<endl;
    }
    return 0;
}
           

hdu 1151:DAG最小覆寫路

hdu 1151

題意:給路口的數量、路的數量及其連通的方式(兩個路口編号)

本質:DAG圖的最小覆寫路

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

int n,m,p[130],mp[130][130],v[130];

bool dfs(int i){
    for(int j=1;j<=n;j++){
        if(mp[i][j]&&!v[j]){
            v[j]=1;
            if(!p[j] || dfs(p[j])){
                p[j]=i;return 1;
            }
        }
    }
    return 0;
}


int main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t;cin>>t;
    while(t--){
        cin>>n>>m;

        memset(mp,0,sizeof mp);
        for(int i=1;i<=m;i++){
            int a,b;cin>>a>>b;
            mp[a][b]=1;
        }
        int ans=0;
        memset(p,0,sizeof p);
        for(int i=1;i<=n;i++){
            memset(v,0,sizeof v);
            if(dfs(i))ans++;
        }
        cout<<n-ans<<endl;
    }
    return 0;
}
           

hdu 1068:最大沒有聯系的男女個數

hdu 1068

本質:最大獨立集 -> 二分圖最大比對

注意:本題并沒有指明男女生的分類,那麼兩個集合采用的均為所有人的編号,最後結果就是:總人數-最大比對數/2

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

int n,ans,p[1000],v[1000],mp[1000][1000];

bool dfs(int i){
    for(int j=0;j<n;j++){
        if(mp[i][j]&&!v[j]){
            v[j]=1;
            if(p[j]==-1||dfs(p[j])){
                p[j]=i;return 1;
            }
        }
    }
    return 0;
}

int main(){
    while(scanf("%d",&n)==1){
        ans=0;
        memset(mp,0, sizeof mp);
        for(int i=0;i<n;i++){
            int k;scanf("%d: (%d)",&i,&k);
            while(k--){
                int a;scanf("%d",&a);
                mp[i][a]=1;
            }
        }
        memset(p,-1,sizeof p);
        for(int i=0;i<n;i++){
            memset(v,0,sizeof v);
            if(dfs(i))ans++;
        }
        printf("%d\n",n-ans/2);
    }
    return 0;
}
           

hdu 1281 :騎士最大放置數

hdu 1281

題意:在一個棋盤裡,給出能放置棋子的格子數以及具體坐标,求騎士最多能多少個(任何棋子之間不能傷害對方);求重要點的數量,重要點:即不放該個格子,騎士最大值就變了

國際象棋中的騎士是橫豎走的,兩個棋子的坐标x互不相同,y坐标互不相同;即可以轉化為集合X 和 集合Y 的最大比對數

求重要點:依次将某個點删去(兩點間的連線由1->0),在進行二分比對操作,看比對書是否改變

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

int n,m,k,ans,p[110],v[110],mp[110][110];

bool dfs(int i){
    for(int j=1;j<=m;j++){
        if(mp[i][j]&&!v[j]){
            v[j]=1;
            if(!p[j] || dfs(p[j])){
                p[j]=i;return 1;
            }
        }
    }
    return 0;
}

int match(){
    int res=0;
    memset(p,0,sizeof p);
    for(int i=1;i<=n;i++){
        memset(v,0,sizeof v);
        if(dfs(i))res++;
    }
    return res;
}

int main(){
    int t=1;
    while(scanf("%d%d%d",&n,&m,&k)==3){
        memset(mp,0,sizeof mp);
        while(k--){
            int x,y;scanf("%d%d",&x,&y);
            mp[x][y]=1;
        }
        ans=match();
        int cnt=0;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                if(mp[i][j]){
                    mp[i][j]=0;
                    if(match()<ans)cnt++;
                    mp[i][j]=1;
                }
            }
        }
        printf("Board %d have %d important blanks for %d chessmen.\n",t++,cnt,ans);
    }
    return 0;
}