二分圖專題

Fire Net hdu1045

題意:在一個最多4×4的方格中,類似於求棋盤問題,只是有些點變成了“牆”,對於同一行或同一列如果有牆,牆兩邊的點是沒有影響的。

思路:對於普通的棋盤問題,只需要把每一行或每一列當成一個點建圖就可以了,但這個因爲有牆的存在顯然不行了,

可以想到對於普通的棋盤問題在沒有牆的時候對於每一行每一列正好是一行一個點,一列一個點,所以也就是連續的 “.” 當成一個點,所以對於這個題也是,對於每一行或者每一列,依然把連續的 " . "當做一個點,直到遇到牆。

這樣處理完後利用重新形成的點建圖,再跑一個匈牙利就可以了。

#include<bits/stdc++.h>
#define exp 1e-8
#define mian main
#define pii pair<int,int>
#define pll pair<ll,ll>
#define ll long long
#define pb push_back
#define PI  acos(-1.0)
#define inf 0x3f3f3f3f
#define w(x) while(x--)
#define int_max 2147483647
#define lowbit(x) (x)&(-x)
#define gcd(a,b) __gcd(a,b)
#define pq(x)  priority_queue<x>
#define ull unsigned long long
#define sc(x) scanf("%d",&x)
#define scl(x) scanf("%lld",&x)
#define pl(a,n) next_permutation(a,a+n)
#define ios ios::sync_with_stdio(false)
#define met(a,x) memset((a),(x),sizeof((a)))
using namespace std;
char a[20][20];
int n,e[20][20],cnt_row,cnt_col,row[20][20],col[20][20];
bool used[20];
int cy[20],cx[20];
bool line(int x)
{
    for(int i=1;i<=cnt_col;i++){
        if(!used[i]&&e[x][i]){
            used[i]=1;
            if(cy[i]==-1||line(cy[i])){
                cy[i]=x;
                return true;
            }
        }
    }
    return false;
}
int maxmatch()
{
    int ans=0;
    met(cy,-1);
    met(cx,-1);
    for(int i=1;i<=cnt_row;i++){
        met(used,0);
        ans+=line(i);
    }
    return ans;
}
int main()
{
    while(~scanf("%d",&n)&&n){
            met(a,0);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
               cin>>a[i][j];
               met(e,0);
                cnt_row=0;
                cnt_col=0;
                met(row,0);
                met(col,0);
        for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++){
            if(a[i][j]=='.'&&row[i][j]==0){ //處理每一行
                cnt_row++;
                for(int k=j;k<=n&&a[i][k]=='.';k++)  //連續的點賦爲一個值
                    row[i][k]=cnt_row;
            }
            if(a[i][j]=='.'&&col[i][j]==0){//處理每一列
                cnt_col++;
                for(int k=i;k<=n&&a[k][j]=='.';k++)
                    col[k][j]=cnt_col;
            }
        }
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                if(a[i][j]=='.')  //當點是"."把行和列兩個點連成一個新邊
            e[row[i][j]][col[i][j]]=1;
        int ans=maxmatch(); //二分匹配
        printf("%d\n",ans);
    }
}

HDU 2444 The Accomodation of Students(BFS/DFS判斷二分圖+最大匹配)

題意:有n個人,m種關係,分別表示兩兩是認識的,關係不能傳遞,問能否把這些人分成兩部分,每一部分的人互相都不認識,如果可以,再問他們上述互相認識的兩人住一個房間,問最多可以分多少個房間?

思路:

題目的題意暗示的已經很明顯了,分成兩部分,分別都不認識,很明顯就是要分成一個二分圖,這時就要判斷這些關係能否形成一個二分圖,這裏可以用BFS染色法(dfs也可),隨便選一個點開始bfs,把這個點先標記爲1,然後與他相連的點標記爲0,依次這樣標記,如果存在某個點與他相連的點標記相同那麼就不能形成二分圖(可以自己畫圖看一看)。

然後就可以用匈牙利了,一開始我分別把標記爲1和0的存在了兩個數組,然後匹配,但WA了,最後改成把這n個點拆點,得出的匹配除2才AC的,求大佬解答一下爲什麼前面那種做法不行......orz

#include<bits/stdc++.h>
#define exp 1e-8
#define mian main
#define pii pair<int,int>
#define pll pair<ll,ll>
#define ll long long
#define pb push_back
#define PI  acos(-1.0)
#define inf 0x3f3f3f3f
#define w(x) while(x--)
#define int_max 2147483647
#define lowbit(x) (x)&(-x)
#define gcd(a,b) __gcd(a,b)
#define pq(x)  priority_queue<x>
#define ull unsigned long long
#define sc(x) scanf("%d",&x)
#define scl(x) scanf("%lld",&x)
#define pl(a,n) next_permutation(a,a+n)
#define ios ios::sync_with_stdio(false)
#define met(a,x) memset((a),(x),sizeof((a)))
using namespace std;
const int N=1e5+10;
struct node
{
    int v,ne;
}e[N];
int ee[510][510];
int head[N];
int a[510],b[510];
int tot,n,m,tot1,tot2;
int vis[510],cy[510];
bool used[510];
bool flag;
bool bfs(int x) //bfs判斷二分圖
{
    queue<int>q;
    q.push(x);
    vis[x]=1;
    while(!q.empty()){
        int u=q.front();
        q.pop();
        int k=vis[u];
        int kk=vis[u]==1?0:1;
        for(int i=head[u];i;i=e[i].ne){
            int v=e[i].v;
            if(vis[v]==k)
                return false;
            if(vis[v]==-1){
                vis[v]=kk;
                q.push(v);
                }
        }
    }
    return true;
}
/*void dfs(int x) //dfs判斷二分圖
{
    if(flag)
        return ;
    int k=vis[x];
    int kk=vis[x]==1?0:1;
    for(int i=head[x];i;i=e[i].ne){
        int v=e[i].v;
        if(vis[v]==-1){
            vis[v]=kk;
            dfs(v);
        }
        else{
            if(vis[v]==k){
                flag=true;
                break;
            }
        }
    }
}*/
void add(int x,int y)
{
    e[++tot].ne=head[x];
    e[tot].v=y;
    head[x]=tot;
}
void init()
{
    met(head,0);
    tot=0;
    met(a,0);
    met(b,0);
    met(ee,0);
    met(vis,-1);
}
bool line(int x)
{
    for(int i=1;i<=n;i++){
        if(!used[i]&&ee[x][i]){
            used[i]=1;
            if(cy[i]==-1||line(cy[i])){
                cy[i]=x;
                return true;
            }
        }
    }
    return false;
}
int maxmatch()
{
    int ans=0;
    met(cy,-1);
    for(int i=1;i<=n;i++){
        met(used,0);
        ans+=line(i);
    }
    return ans;
}
int main()
{
    while(~scanf("%d%d",&n,&m)){
            init();
        int x,y;
        int t;
        for(int i=1;i<=m;i++){
            scanf("%d%d",&x,&y);
            t=x;
            ee[x][y]=1;
            ee[y][x]=1;
            add(x,y);
            add(y,x);
        }
        if(bfs(t)){
            printf("%d\n",maxmatch()/2);
        }
        else
            printf("No\n");
    }
}

Swap HDU - 2819

題意:給一個N*N的矩陣,值只能是1或0,問能否經過多次交換行和列使對角線上全爲1?

若能,給出交換策略、不能 輸出-1

思路:

首先分析什麼時候可以交換成功,如果任意一行或任意一列全爲0,那麼肯定就不行,即只要每一行和每一列都有1就肯定可以

但如果只是這樣的話,爲什麼要二分圖呢?因爲還要給出怎樣交換的,這個時候匈牙利算法中順帶求出的用來儲存哪個點和哪個點匹配的數組就有用處了,跑完一遍匈牙利後,根據得出的數組,對於n行依次遍歷(爲什麼可以依次遍歷?大家可以畫畫圖想一下,題目雖說行和列都可交換,但其實只需只交換行或者只交換列即可),先判斷每一行的點匹配的是否等於這一行的序號(即第幾行),如果不等於,找出需要和哪一行換,把要交換的信息存到數組中即可。

#include<bits/stdc++.h>
#define exp 1e-8
#define mian main
#define pii pair<int,int>
#define pll pair<ll,ll>
#define ll long long
#define pb push_back
#define PI  acos(-1.0)
#define inf 0x3f3f3f3f
#define w(x) while(x--)
#define int_max 2147483647
#define lowbit(x) (x)&(-x)
#define gcd(a,b) __gcd(a,b)
#define pq(x)  priority_queue<x>
#define ull unsigned long long
#define sc(x) scanf("%d",&x)
#define scl(x) scanf("%lld",&x)
#define pl(a,n) next_permutation(a,a+n)
#define ios ios::sync_with_stdio(false)
#define met(a,x) memset((a),(x),sizeof((a)))
using namespace std;
const int N=110;
int e[N][N];
int n;
bool used[N];
int cx[N],cy[N];
int a[N],b[N];
bool line(int x)
{
    for(int i=1;i<=n;i++){
        if(!used[i]&&e[x][i]){
            used[i]=1;
            if(cy[i]==-1||line(cy[i])){
                cy[i]=x;
                cx[x]=i;
                return true;
            }
        }
    }
    return false;
}
int maxmatch()
{
    int ans=0;
    met(cx,-1);
    met(cy,-1);
    for(int i=1;i<=n;i++){
        met(used,0);
        ans+=line(i);
    }
    return ans;
}
int main()
{
    while(~scanf("%d",&n)){
            met(e,0);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                scanf("%d",&e[i][j]);
            int num=maxmatch();
            if(num==n){  //最大匹配等於N纔可以
                int ans=0;
                for(int i=1;i<=n;i++){
                        int k=cx[i];//這一行匹配的是第幾列
                        int kk=cy[i];//第I行要匹配的那一列在第幾行
                        if(k==i)
                            continue;
                            a[++ans]=i;
                            b[ans]=kk;
                        swap(cx[i],cx[kk]);
                        swap(cy[i],cy[k]);
                }
                printf("%d\n",ans);
                for(int i=1;i<=ans;i++)
                    printf("R %d %d\n",a[i],b[i]);
            }
            else printf("-1\n");
    }
}

奔小康賺大錢

題意:一道模板題,n個村民分配房子,房子正好也是n個,每個村民對不同的房子會出不同的價格,問最大可以獲得多大的利潤?

思路:詳解可以參照我轉載的一篇博客,另外我的代碼裏的註釋是學習時看的博客上舉的例子的註釋,讀者可以自行把那些男生,女生換成村民和房子(逃...

#include<bits/stdc++.h>
#define exp 1e-8
#define mian main
#define pii pair<int,int>
#define pll pair<ll,ll>
#define ll long long
#define pb push_back
#define PI  acos(-1.0)
#define inf 0x3f3f3f3f
#define w(x) while(x--)
#define int_max 2147483647
#define lowbit(x) (x)&(-x)
#define gcd(a,b) __gcd(a,b)
#define pq(x)  priority_queue<x>
#define ull unsigned long long
#define sc(x) scanf("%d",&x)
#define scl(x) scanf("%lld",&x)
#define pl(a,n) next_permutation(a,a+n)
#define ios ios::sync_with_stdio(false)
#define met(a,x) memset((a),(x),sizeof((a)))
using namespace std;
const int N=310;
int n;
int e[N][N]; //關係
int ex_girl[N];//女生的期望值
int ex_boy[N];//男生的期望值
int match[N]; //記錄每個男生匹配到的女生,如果沒有爲-1
bool girl[N];//標記每一輪匹配過的女生
bool boy[N];//標記每一輪匹配過的男生
int slack[N];//記錄每個男生要被女生選中還需多少期望值
bool line(int x)
{
    girl[x]=1;
    for(int i=1;i<=n;i++){
        if(boy[i])
            continue;
        int k=ex_boy[i]+ex_girl[x]-e[x][i];
        if(k==0){ //符合要求
            boy[i]=1;
            if(match[i]==-1||line(match[i])){
                match[i]=x;
                return true;
            }
        }
        else slack[i]=min(slack[i],k);
    }
    return false;
}
int km()
{
    met(ex_boy,0);
    met(match,-1);
    for(int i=1;i<=n;i++){
        ex_girl[i]=e[i][1];
        for(int j=2;j<=n;j++)
            ex_girl[i]=max(ex_girl[i],e[i][j]);  //初始每個女生的期望值爲對每個男生的期望的最大值
    }
    for(int i=1;i<=n;i++){  //爲每一個女生匹配
        met(slack,inf);
        while(1){
            met(girl,0);
            met(boy,0);
            if(line(i))  //匹配成功,退出
                break;
            int d=inf; //匹配失敗
            for(int j=1;j<=n;j++)
                if(!boy[j])
                d=min(d,slack[j]);  //最小可降低的期望值
            for(int j=1;j<=n;j++){
                if(girl[j])
                    ex_girl[j]-=d;  //所以找到過的女生降低期望值
                if(boy[j])
                    ex_boy[j]+=d; //所有找到過的男生增加期望值
                else slack[j]-=d; //沒有訪問過的boy 因爲girl們的期望值降低,距離得到女生傾心又進了一步!
            }
        }
    }
    int ans=0;
    for(int i=1;i<=n;i++)
        ans+=e[match[i]][i];
    return ans;
}
int main()
{
    while(~scanf("%d",&n)){
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
            scanf("%d",&e[i][j]);
        printf("%d\n",km());
    }
}

 

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