二分图匹配 --- 最小点覆盖

//二分图有一个重要模型 – 最小点覆盖.
结论 : 最小点覆盖 = 二分图最大匹配数.

解释: 最小点覆盖指的是选择尽量少的点, 使得每条边至少有一个端点被选中. 那么在二分图匹配中很容易可以被证明就是该个二分图的最匹配数.
(把图画出来写一写就知道了.)

举几个例子 :

经典例题UVa – 11419
//题意: 在一个矩阵上某些位置上有一些目标, 每一次可以发射一颗子弹, 这颗子弹可以把任意一行或一列上的目标打完, 问最少需要几颗子弹, 并输出这些子弹发射的位置.
//思路 : 把每一行看做一个X结点, 每一列看做一个Y结点, 每个目标所在的位置对应的点有一条边, 这样, 子弹打掉所有的目标意味着每条边值得至少有一个结点被选中. 即可以从该点打出子弹可以清除该个目标, 这样最小点覆盖数就是答案, 跑一遍匈牙利就可以出答案了.
//但是麻烦的是还要输出打子弹的位置. 这个就比较难搞了. 白书上说的是什么匈牙利树, 不懂. 然后看了很多博客, 把代码仔细研究研究, 懂了. 大概就是从左边没有覆盖的点再找增广路, 并且这个未被覆盖的x点不管有无边, 我们最后都一定不会选它. 找到增广路后再从右边的那个点开始找, (此时找到的右边的那个点一定是匹配了的), 再找起增广路, 此时一定是还有的, 因为匹配了的, 这样我们我们可以发现右边的那个点连接着两个左边的点, 对应在图中肯定在该点处放子弹是最优的, 所以一直进行下去就可以找到所有最优的点, 然后输出就可以了.

AC Code

/** @Cain*/
const int maxn=1e3+5;
bool g[maxn][maxn];
bool visx[maxn],visy[maxn];
int linkx[maxn],linky[maxn];
int n,m;
bool dfs(int x)
{
    visx[x] = true;
    for(int i=1;i<=m;i++){
        if(visy[i] || !g[x][i]) continue;
        visy[i] = true;
        if(linky[i] == -1 || dfs(linky[i])){
            linky[i] = x;
            linkx[x] = i;
            return true;
        }
    }
    return false;
}
void solve()
{
    int k;
    while(~scanf("%d%d%d",&n,&m,&k)){
        if(n + m +k == 0) break;
        Fill(g,false); Fill(linkx,-1); Fill(linky,-1);
        for(int i=1;i<=k;i++){
            int u,v; scanf("%d%d",&u,&v);
            g[u][v] = true;
        }
        int res = 0;
        for(int i=1;i<=n;i++){
            Fill(visy,false);
            if(dfs(i)) res++;
        }
        printf("%d",res);
        /*这里就是checkx集合中的点。如果这个点被匹配了,先不管
        然后就是check未匹配的点,不管有无其他点与其连接,这里都
        显然不用放炮弹. 然后剩余的操作就是我说的那样*/
        Fill(visx,false); Fill(visy,false);
        for(int i=1;i<=n;i++){
            if(linkx[i] == -1) dfs(i);
        }
        bool flag = false;
        for(int i=1;i<=n;i++){
            if(!visx[i]){ printf(" r%d",i); flag = true; }
        }
        if(flag) printf("\n");
        flag = false;
        for(int i=1;i<=n;i++){
            if(visy[i]){ printf(" c%d",i); flag = true; }
        }
        if(flag) printf("\n");
    }
}

福工OJ – 2573
//有了上面这道的基础, 这就是一道水题, 直接做.
AC Code

/** @Cain*/
const int maxn=1e2+5;
bool g[maxn][maxn],vis[maxn];
int link[maxn];
char a[maxn][maxn];
int n,m;
bool dfs(int x)
{
    for(int i=1;i<=m;i++){
        if(vis[i] || !g[x][i]) continue;
        vis[i] = true;
        if(link[i] == -1 || dfs(link[i])){
            link[i] = x;
            return true;
        }
    }
    return false;
}
void solve()
{
    while(~scanf("%d",&n) && n){
        scanf("%d",&m);
        Fill(g,false); Fill(link,-1);
        for(int i=1;i<=n;i++) scanf("%s",a[i]+1);
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                int u = a[i][j]-'0';
                if(u == 1) g[i][j] = true;
            }
        }
        int res = 0;
        for(int i=1;i<=n;i++){
            Fill(vis,false);
            if(dfs(i)){
                res++;
            }
        }
        printf("%d\n",res);
    }
}

你可能感兴趣的:(二分图)