poj3041--二分图匹配算法

转自网络:

解题思路:

把方阵看做一个特殊的二分图(以行列分别作为两个顶点集V1、V2,其中| V1|=| V2|)

然后把每行x或者每列y看成一个点,而障碍物(x,y)可以看做连接x和y的边。按照这种思路构图后。问题就转化成为选择最少的一些点(x或y),使得从这些点与所有的边相邻,其实这就是最小点覆盖问题。

 

再利用二分图最大匹配的König定理:

最小点覆盖数 = 最大匹配数

 

(PS:最小点覆盖:假如选了一个点就相当于覆盖了以它为端点的所有边,你需要选择最少的点来覆盖图的所有的边。)

 

因此本题自然转化为求 二分图的最大匹配 问题

 

 

求最大匹配的一种显而易见的算法是:先找出全部匹配,然后保留匹配数最多的。但是这个算法的时间复杂度为边数的指数级函数

因此,需要寻求一种更加高效的算法——增广路求最大匹配的方法(匈牙利算法)

 

增广路的定义(也称增广轨或交错轨):

若P是图G中一条连通两个未匹配顶点的路径,并且属于M的边和不属于M的边(即已匹配和待匹配的边)在P上交替出现,则称P为相对于M的一条增广路径。

 

 由增广路的定义可以推出下述三个结论:

  1、P的路径个数必定为奇数,第一条边和最后一条边都不属于M。

2、将M和P进行取反操作可以得到一个更大的匹配M’

   (反操作:把P中的 匹配边 与 非匹配边 互换)

3、M为G的最大匹配当且仅当不存在M的增广路径P

 

 匈牙利算法轮廓:

  (1)置M为空

  (2)找出一条增广路径P,通过异或操作获得更大的匹配M’代替M

(3)重复(2)操作直到找不出增广路径为止


PS:要提醒的是最小点覆盖的解释,当图经过匈牙利算法搜索之后,图中只会存在这样的两种边。匹配点--匹配点,匹配点-未匹配点。
那么也就是说,所有的边都有匹配点。这也就符合了最小点覆盖的定义。用最小的点覆盖所有的边。 
这题的代码有两种,一种是用匈牙利算法,另一种是 hopcroft-carp算法。
还有要说的就是图论题建图是个难题。这题利用的是把行列当做结点。很是巧妙。
 匈牙利算法:

#include<stdio.h>
#include<string.h>
#include <iostream>
using namespace std;
#define maxn 500
int edge[maxn][maxn];
int gril[maxn];
int boy[maxn];
int used[maxn];
int ans=0;
int v1,v2;
int dfs(int x)
{
    for(int i=1;i<=v2;i++)
    {
        if(edge[x][i]==1&&used[i]==0)
        {
            used[i]=1;
            if(gril[i]==0||dfs(gril[i]))
            {
                gril[i]=x;
                return 1;
            }
        }
    }
    return 0;
}
void hungrian()
{
    for(int i=1;i<=v1;i++)
    {
        memset(used,0,sizeof(used));
        if(dfs(i))
            ans++;
    }
}
int main()
{
    int m,k;
    int x,y;
    scanf("%d %d",&m,&k);
    v1=v2=m;
    memset(edge,0,sizeof(edge));
    for(int i=1;i<=k;i++)
    {
        scanf("%d %d",&x,&y);
        edge[x][y]=1;
    }
    hungrian();
    printf("%d\n",ans);
}

hopcroft-carp算法

#include<bits/stdc++.h>
using namespace std;
#define maxn 500
#define inf INT_MAX
int bmap[maxn][maxn];
int cy[maxn];
int cx[maxn];
int nx,ny;
int dx[maxn];
int dy[maxn];
int dis;
bool bmask[maxn];
bool ser()
{
    queue<int>Q;
    dis=inf;
    memset(dx,-1,sizeof(dx));
    memset(dy,-1,sizeof(dy));
    for(int i=1;i<=nx;i++)
    {
        if(cx[i]==-1)
        {
            Q.push(i);
            dx[i]=0;
        }
    }
    while(!Q.empty())
    {
        int u=Q.front();
        Q.pop();
        for(int v=1;v<=ny;v++)
        {
            if(bmap[u][v]&&dy[v]==-1)
            {
                dy[v]=dx[u]+1;
                if(cy[v]==-1)dis=dy[v];
                else
                {
                    dx[cy[v]]=dy[v]+1;
                    Q.push(cy[v]);
                }
            }
        }
    }
    return dis!=inf;
}
int findpath(int u)
{
    for(int v=1;v<=ny;v++)
    {
        if(!bmask[v]&&bmap[u][v]&&dy[v]==dx[u]+1)
        {
            bmask[v]=1;
            if(cy[v]!=-1&&dy[v]==dis)
            {
                continue;
            }
            if(cy[v]==-1||findpath(cy[v]))
            {
                cy[v]=u;
                cx[u]=v;
                return 1;
            }
        }
    }
    return 0;
}
int maxmatch()
{
    int res=0;
    memset(cx,-1,sizeof(cx));
    memset(cy,-1,sizeof(cy));
    while(ser())
    {
        memset(bmask,0,sizeof(bmask));
        for(int i=1;i<=nx;i++)
        {
            if(cx[i]==-1)
            {
                res+=findpath(i);
            }
        }
    }
    return res;
}
int main()
{
    int m,k;
    int x,y;
    scanf("%d %d",&m,&k);
    nx=ny=m;
    memset(bmap,0,sizeof(bmap));
    for(int i=1;i<=k;i++)
    {
        scanf("%d %d",&x,&y);
        bmap[x][y]=1;
    }
    printf("%d\n",maxmatch());
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值