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

来源:互联网 发布:手机智能机器人软件 编辑:程序博客网 时间:2024/05/16 06:18

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

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

举几个例子 :

经典例题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);    }}
原创粉丝点击