匹配及其相关问题(三)

21 篇文章 0 订阅
9 篇文章 0 订阅

前言:
第二篇博客介绍了匈牙利算法解二分图最大匹配,这次我们需要应用这个算法来解决最小点覆盖最大点独立问题。

基本定理:
根据博客(一),我们有以下定理:
定理3:二分图中,无孤立点,点覆盖数=边独立数(匹配数)
定理8:无向图中,无孤立点,最小点覆盖集与最大点独立集互补
我们这次需要使用这两个定理来解决最小点覆盖与最大点独立问题。

算法思路:
根据定理3,我们有点覆盖数等于匹配数,那么我们可以认为,最小点覆盖集与最大匹配之间有一种一一对应的关系。
最小点覆盖:
在求解完二分图匹配之后,我们可以知道现在二分图中已经没有增广路了,而且只存在两种边,第一种为两端都是已匹配点,第二种为一端已匹配、另一端未匹配。那么我们要找的最小点覆盖集肯定可以在所有的匹配点里找到,即对第一种边,随便挑一个点;对第二种边,挑匹配点。
那么我们怎么选这些匹配点呢?因为所有与未匹配点相连的点都应该是最小点覆盖集里的点,那么跟这些点匹配的点当然是不需要在最小点覆盖集里了。我们可以把二分图分成两部分,即左边和右边,我们可以使用匈牙利算法从未匹配点开始寻找增广路,对图中的点进行标记,最终左边未标记的点和右边标记了的点组成了最小点覆盖。
其实也可以分类讨论考虑最小点覆盖问题,即左边有未标记点和左边无未标记点。对于第一种情况,我直接左边的所有点组成的点集组成最小点覆盖;对于第二种情况,我们则是为了找到所有能覆盖未标记点所在的边而进行匈牙利的match操作,即我们发现最小点覆盖中不可能有这个点,于是就去找能够覆盖该点所有关联边的代替点集合。
最大点独立:
既然我们知道最小点覆盖,那么根据定理8,我们只需要求出最小点覆盖的补集就是最大点独立了,即标记了的左边点和未标记的右边点。

例题解析:
1、二分图最小点覆盖:
题目链接:UVA 11419
解题思路:
二分图最小点覆盖模板题。把每一行变成左边的点,每一列变成右边的点,这样就变成了最小点覆盖问题,可用上述算法解决。
代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define maxn 1005

using namespace std;

int n,m,K,a[maxn][maxn],vx[maxn],vy[maxn],prex[maxn],prey[maxn];

bool dfs(int index)
{
    vx[index]=1;
    for(int i=1;i<=m;i++)
    {
        if(!vy[i]&&a[index][i])
        {
            vy[i]=1;
            if(!prey[i] || dfs(prey[i]))
            {
                prey[i]=index;
                prex[index]=i;
                return 1;
            }
        }
    }
    return 0;
}

int hungary()
{
    int ans=0;
    memset(prex,0,sizeof(prex));
    memset(prey,0,sizeof(prey));
    for(int i=1;i<=n;i++)
    {
        memset(vy,0,sizeof(vy));
        if(dfs(i))
            ans++;
    }
    return ans;
}

int solve()
{
    printf("%d",hungary());
    memset(vx,0,sizeof(vx));
    memset(vy,0,sizeof(vy));
    for(int i=1;i<=n;i++)
        if(!prex[i])
            dfs(i);
    for(int i=1;i<=n;i++)
        if(!vx[i])
            printf(" r%d",i);
    for(int i=1;i<=m;i++)
        if(vy[i])
            printf(" c%d",i);
    printf("\n");
}

int main()
{
    while(~scanf("%d %d %d",&n,&m,&K)&&n+m+K)
    {
        memset(a,0,sizeof(a));
        int x,y;
        while(K--)
        {
            scanf("%d %d",&x,&y);
            a[x][y]=1;
        }
        solve();
    }

    return 0;
}

2、二分图最大点独立:
题目链接:UVALIVE 4288
解题思路:
二分图最大点独立,因为只需要求点独立数,所以直接套用匈牙利算法即可。构图方面,把所有人分成两类,喜欢猫的和喜欢狗的,形成二分图,并把所有存在矛盾关系的人连一条边,即讨厌对方喜欢的动物或者喜欢对方讨厌的动物。
代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define maxn 505

using namespace std;

int n,m,c,d,K,a[maxn][maxn],vy[maxn],prey[maxn];
int likec[maxn],liked[maxn],hatec[maxn],hated[maxn];

bool dfs(int index)
{
    for(int i=1;i<=m;i++)
    {
        if(!vy[i]&&a[index][i])
        {
            vy[i]=1;
            if(!prey[i] || dfs(prey[i]))
            {
                prey[i]=index;
                return 1;
            }
        }
    }
    return 0;
}

int hungary()
{
    int ans=0;
    memset(prey,0,sizeof(prey));
    for(int i=1;i<=n;i++)
    {
        memset(vy,0,sizeof(vy));
        if(dfs(i))
            ans++;
    }
    return ans;
}

int main()
{
    int T;
    scanf("%d",&T);
    getchar();
    while(T--)
    {
        n=m=0;
        scanf("%d %d %d",&c,&d,&K);
        getchar();
        char ch1,ch2;
        int x,y;
        for(int i=1;i<=K;i++)
        {
            scanf("%c%d %c%d",&ch1,&x,&ch2,&y);
            if(ch1=='C')
            {
                n++;
                likec[n]=x,hated[n]=y;
            }
            else
            {
                m++;                
                liked[m]=x,hatec[m]=y;
            }
            getchar();
        }

        memset(a,0,sizeof(a));
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                if(likec[i]==hatec[j] || liked[j]==hated[i])
                {
                    a[i][j]=1;
                }
            }
        }

        int ans=hungary();
        printf("%d\n",n+m-ans);
    }

    return 0;
}

参考资料:
Matrix 67的博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值