二分圖 匈牙利算法應用

匈牙利算法是kuangbin的

poj 1274 poj 2239 //水題

poj 2584

這個還是有點想法的,把每種衣服拆成n件衣服(每件衣服都是一個單獨的節點),然後依次連接


下面是代碼,思路還是比較清晰的


#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;


/* **************************************************************************
//二分圖匹配(匈牙利算法的DFS實現)
//初始化:g[][]兩邊頂點的劃分情況
//建立g[i][j]表示i->j的有向邊就可以了,是左邊向右邊的匹配
//g沒有邊相連則初始化爲0
//uN是匹配左邊的頂點數,vN是匹配右邊的頂點數
//調用:res=hungary();輸出最大匹配數
//優點:適用於稠密圖,DFS找增廣路,實現簡潔易於理解
//時間複雜度:O(VE)
//***************************************************************************/
//頂點編號從0開始的
const int MAXN=400;
int uN,vN;//u,v數目
int g[MAXN][MAXN];
int linker[MAXN];
bool used[MAXN];
bool dfs(int u)//從左邊開始找增廣路徑
{
    int v;
    for(v=0;v<vN;v++)//這個頂點編號從0開始,若要從1開始需要修改
      if(g[u][v]&&!used[v])
      {
          used[v]=true;
          if(linker[v]==-1||dfs(linker[v]))
          {//找增廣路,反向
              linker[v]=u;
              return true;
          }
      }
    return false;//這個不要忘了,經常忘記這句
}
int hungary()//xiongyali
{
    int res=0;
    int u;
    memset(linker,-1,sizeof(linker));
    for(u=0;u<uN;u++)
    {
        memset(used,0,sizeof(used));
        if(dfs(u)) res++;
    }
    return res;
}
//******************************************************************************/

int F[100],A[100],tmp[50];
int judge(char t){
    if(t=='S')return 1;
    else if(t=='M')return 2;
    else if(t=='L')return 3;
    else if(t=='X')return 4;
    else return 5;

}
int main()
{
     int v;
     int k,k1,k2;
     char str[30];
     while(~scanf("%s",&str)&&strcmp(str,"ENDOFINPUT")!=0)
     {
         scanf("%d",&uN);
         memset(g,0,sizeof(g));
         getchar();
         for(int i=0;i<uN;i++)
         {
             char t1,t2;
             scanf("%c%c",&t1,&t2);
             getchar();
             F[i]=judge(t1);
             A[i]=judge(t2);

         }
         memset(tmp,0,sizeof(tmp));
         for(int i=1;i<=5;i++){
            scanf("%d",&tmp[i]);
            tmp[i]+=tmp[i-1];
         }
         for(int i=0;i<uN;i++){
            for(int j=F[i];j<=A[i];j++){
                for(int k=tmp[j-1]+1;k<=tmp[j];k++)g[i][k]=1;
            }
         }
         vN=tmp[5]+20;//剛開始這裏寫的是6*12+11,WA,後來又+5,莫名其妙AC了,我也不知道爲什麼......
         int re=hungary();
         getchar();
         scanf("%s",&str);
         getchar();
         if(re==uN)printf("T-shirts rock!\n");
         else printf("I'd rather not wear a shirt anyway...\n");

     }
     return 0;
}

poj 2536

這題是水題,可是老是A不了,我也不知道爲什麼,我的思路和代碼風格完全和題解並沒有太大區別

poj 2446

這題我是這樣寫的

然後老是WA怎麼也改不過來......

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;


/* **************************************************************************
//二分圖匹配(匈牙利算法的DFS實現)
//初始化:g[][]兩邊頂點的劃分情況
//建立g[i][j]表示i->j的有向邊就可以了,是左邊向右邊的匹配
//g沒有邊相連則初始化爲0
//uN是匹配左邊的頂點數,vN是匹配右邊的頂點數
//調用:res=hungary();輸出最大匹配數
//優點:適用於稠密圖,DFS找增廣路,實現簡潔易於理解
//時間複雜度:O(VE)
//***************************************************************************/
//頂點編號從0開始的
const int MAXN=1500+50;
int uN,vN;//u,v數目
int g[MAXN][MAXN];
int linker[MAXN];
bool used[MAXN];
int maze[500][500];
bool dfs(int u)//從左邊開始找增廣路徑
{
    int v;
    for(v=0;v<vN;v++)//這個頂點編號從0開始,若要從1開始需要修改
      if(g[u][v]&&!used[v])
      {
          used[v]=true;
          if(linker[v]==-1||dfs(linker[v]))
          {//找增廣路,反向
              linker[v]=u;
              return true;
          }
      }
    return false;//這個不要忘了,經常忘記這句
}
int hungary()//xiongyali
{
    int res=0;
    int u;
    memset(linker,-1,sizeof(linker));
    for(u=0;u<uN;u++)
    {
        memset(used,0,sizeof(used));
        if(dfs(u))res++;
    }
    return res;
}
//******************************************************************************/
int n,m;
int dir[4][2]={{0,1},{0,-1},{1,0},{-1,0}};
bool ok(int x,int y){
    if(x<=0||x>n||y<=0||y>m||maze[x][y]==-1)return false;
    return true;
}
void deal(){
    uN=n*m+5;
    vN=n*m+5;
    memset(g,0,sizeof(g));
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            for(int k=0;k<4;k++){
                int x=i+dir[k][0],y=j+dir[k][1];
                if(ok(x,y))g[(i-1)*m+j-1][(x-1)*m+y-1]=1;
            }
        }
    }

}


int main()
{
    int k,x,y;
     while(~scanf("%d%d%d",&n,&m,&k)){
         memset(maze,0,sizeof(maze));

        for(int i=1;i<=k;i++){
            scanf("%d%d",&y,&x);
            maze[x][y]=-1;
        }
        deal();
        int re=hungary();
        if((n*m-k)%2)printf("NO\n");
        else if(re+k==n*m)printf("YES\n");
        else printf("NO\n");
     }
}

然後我就看了看kuangbin的思路,沒辦法,改的和他一樣過了......

其實我也不知道我之前的思路到低哪裏不對了,現在也不知道

以下是正確代碼

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
const int MAXN=2500+50;
int uN,vN;//u,v數目
int g[MAXN][MAXN];
int linker[MAXN];
bool used[MAXN];
int maze[500][500],num[50][50];
bool dfs(int u)//從左邊開始找增廣路徑
{
    int v;
    for(v=0;v<vN;v++)//這個頂點編號從0開始,若要從1開始需要修改
      if(g[u][v]&&!used[v])
      {
          used[v]=true;
          if(linker[v]==-1||dfs(linker[v]))
          {//找增廣路,反向
              linker[v]=u;
              return true;
          }
      }
    return false;//這個不要忘了,經常忘記這句
}
int hungary()//xiongyali
{
    int res=0;
    int u;
    memset(linker,-1,sizeof(linker));
    for(u=0;u<uN;u++)
    {
        memset(used,0,sizeof(used));
        if(dfs(u))res++;
    }
    return res;
}
//******************************************************************************/
int n,m;
int dir[4][2]={{0,1},{0,-1},{1,0},{-1,0}};
bool ok(int x,int y){
    if(x<=0||x>n||y<=0||y>m||maze[x][y]==-1)return false;
    return true;
}



int main()
{
     int k,x,y;
     while(~scanf("%d%d%d",&n,&m,&k)){
         memset(maze,0,sizeof(maze));
         memset(num,0,sizeof(num));
         memset(g,0,sizeof(g));
        for(int i=1;i<=k;i++){
            scanf("%d%d",&y,&x);
            maze[x][y]=-1;
        }
        int tol=0;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                if(maze[i][j]!=-1)num[i][j]=tol++;
            }
        }
        uN=vN=tol;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                if(maze[i][j]!=-1)
                {
                  int u=num[i][j];
                  if(i-1>0&&maze[i-1][j]!=-1)g[u][num[i-1][j]]=1;
                  if(j-1>0&&maze[i][j-1]!=-1)g[u][num[i][j-1]]=1;
                  if(i+1<=n&&maze[i+1][j]!=-1)g[u][num[i+1][j]]=1;
                  if(j+1<=m&&maze[i][j+1]!=-1)g[u][num[i][j+1]]=1;
                }




        int re=hungary();
        if((n*m-k)%2)printf("NO\n");
        else if(re==tol)printf("YES\n");
        else printf("NO\n");
     }
}

poj 3041

這個題我要好好寫寫題解(其實是看別人的)我自己想了想,感覺和二分圖實在是扯不上關係......

這是別人的說法

這個題目屬於標準的圖論題目,需要適當的轉換,建立適當的圖來進行結題。建立二分圖,設置x座標作爲A點集,y座標作爲B點集。那麼每個點轉化成連接A、B點集的邊。問題就轉化成最小點覆蓋問題,即若選定一個點,那麼於此點相連的所有邊都被選定,求滿足選定所有邊的最少的點集。最小點覆蓋問題可以用二分圖的最大匹配來解。最小點數=最大匹配數(此處不在證明,詳情請看上篇文章)。這樣通過構建二分圖求解最大匹配數,來求解該問題的最小點覆蓋。

然後這題就是大水題了......


這個題之前見過,總想着是不是個貪心,前人經驗證明貪心不可做

以下是代碼

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
const int MAXN=2500+50;
int uN,vN;//u,v數目
int g[MAXN][MAXN];
int linker[MAXN];
bool used[MAXN];
int maze[500][500],num[50][50];
bool dfs(int u)//從左邊開始找增廣路徑
{
    int v;
    for(v=0;v<vN;v++)//這個頂點編號從0開始,若要從1開始需要修改
      if(g[u][v]&&!used[v])
      {
          used[v]=true;
          if(linker[v]==-1||dfs(linker[v]))
          {//找增廣路,反向
              linker[v]=u;
              return true;
          }
      }
    return false;//這個不要忘了,經常忘記這句
}
int hungary()//xiongyali
{
    int res=0;
    int u;
    memset(linker,-1,sizeof(linker));
    for(u=0;u<uN;u++)
    {
        memset(used,0,sizeof(used));
        if(dfs(u))res++;
    }
    return res;
}
//******************************************************************************/
int n,m;
int dir[4][2]={{0,1},{0,-1},{1,0},{-1,0}};
bool ok(int x,int y){
    if(x<=0||x>n||y<=0||y>m||maze[x][y]==-1)return false;
    return true;
}



int main()
{
    int n,k,x,y;
     while(~scanf("%d%d",&n,&k)){
            memset(g,0,sizeof(g));
            uN=n+1,vN=n+1;
        while(k--){
            scanf("%d%d",&x,&y);
            g[x--][y--]=1;
        }
     printf("%d\n",hungary());
     }
}

poj 1325

這題不難,不過有個地方需要注意

就是初始狀態兩個機器都是狀態0,所以有個事要注意,就是所有和0相連的的邊都要刪掉......(就因爲這個WA了幾次)


poj 2226

這個題挺難想的

我是看別人的題解看出來的

以下是別人的分析

題意:R*C的矩陣表示一塊田地,'*'表示溼地,'.'表示草地。現在FJ要在田地上鋪木板蓋掉所有的溼地,露出所有的草地。每塊木板的寬度爲1,長度爲任意長。問FJ最少用幾塊木板就可以完成任務?

思路:看了半天,實在是沒思路,看了看別人的報告才明白。把它轉化爲一個二分圖,求最小點覆蓋。
但如何構成二分圖呢??
就是把每一行中不相連的一塊區域看成一個點,同理每一列也一樣。然後就有點像3041那題一樣。求它的最小點覆蓋

*.*.   按行1  0  2  0    按列 1  0  4  0

.***           0  3  3  3             0  3  4  5

***.           4  4  4  0             2  3  4  0

..*.            0   0  5  0            0  0  4  0

下面是代碼

#include<iostream>
#include<cstdio>
#include<cstring>

using namespace std;

int n,m,cnt1,cnt2,map[2520][2520],linker[2520],vis[2520];
int g1[60][60],g2[60][60],tg[60][60];
char str[60][60];

int DFS(int u){
    int v;
    for(v=1;v<=cnt2;v++)
        if(map[u][v] && !vis[v]){
            vis[v]=1;
            if(linker[v]==-1 || DFS(linker[v])){
                linker[v]=u;
                return 1;
            }
        }
    return 0;
}

int Hungary(){
    int u,ans=0;
    memset(linker,-1,sizeof(linker));
    for(u=1;u<=cnt1;u++){
        memset(vis,0,sizeof(vis));
        if(DFS(u))
            ans++;
    }
    return ans;
}

int main(){

    //freopen("input.txt","r",stdin);

    while(~scanf("%d%d",&n,&m)){
        for(int i=0;i<n;i++)
            scanf("%s",str[i]);
        memset(g1,0,sizeof(g1));
        memset(g2,0,sizeof(g2));
        cnt1=0;
        for(int i=0;i<n;i++)
            for(int j=0;j<m;j++){
                if(str[i][j]=='*'){
                    if(j==0)
                        g1[i][j]=++cnt1;
                    else if(str[i][j-1]=='*')
                        g1[i][j]=g1[i][j-1];
                    else
                        g1[i][j]=++cnt1;
                }
            }
        cnt2=0;
        for(int j=0;j<m;j++)    //注意這裏
            for(int i=0;i<n;i++){
                if(str[i][j]=='*'){
                    if(i==0)
                        g2[i][j]=++cnt2;
                    else if(str[i-1][j]=='*')
                        g2[i][j]=g2[i-1][j];
                    else
                        g2[i][j]=++cnt2;
                }
            }
        memset(map,0,sizeof(map));
        for(int i=0;i<n;i++)
            for(int j=0;j<m;j++)
                if(str[i][j]=='*')
                    map[g1[i][j]][g2[i][j]]=1;
        int ans=Hungary();
        printf("%d\n",ans);
    }
    return 0;
}
但是其實精髓還是不由完全掌握,以後遇見還要注意......



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