二分图 匈牙利算法应用

匈牙利算法是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;
}
但是其实精髓还是不由完全掌握,以后遇见还要注意......



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值