POJ 3020 Antenna Placement 匈牙利算法

来源:互联网 发布:数据安全 编辑:程序博客网 时间:2024/05/22 01:45

这是本文的第三版:

先给出问题的等价描述:平面直角坐标,做出所有 x = 整数,y = 整数的直线,相交形成网格。给出在格点上的一些城市。给定一种长度为1,两个端点都在格点上的线段。每个城市至少连接一个这样的线段,求线段的最少数量。

本题实际在求无向图的最小边覆盖。(很多博客说是最小路径覆盖,但公式是正确的。可能是混淆了这两个名词的概念)


预备知识:

二分图和匹配:http://www.renfei.org/blog/bipartite-matching.html(后面的定理暂时不管)

匈牙利算法:http://blog.csdn.net/dark_scope/article/details/8880547


本题使用的定理:

二分图最小边覆盖 = 顶点数 - 最大匹配数

设 G 为根据无向图建立的二分图

无向图的最小边覆盖 = ( G 的最小边覆盖) / 2


正文开始:

其实代码中没有真的建立原无向图。这个原无向图存在于我们的分析中。在原无向图中,任意两个相邻(距离为 1)的结点之间都有一条无向边。

为什么是在求原无向图的最小边覆盖?

看这组样例

**

**


先把 1,2连起来。下面有两种连法。可以把 1,3 连起来,再给 4 一个单独的线段,这样一共需要三个线段。也可以把1,2连起来,再把 3,4 连起来,这样只需要两条线段。为什么第一种方法比第二种方法的线段多?因为第一种方法 1 上有两条线段,产生了浪费,可以得出结论,最少的连接方式,一定保证每个结点连接的线段数量恰好等于1(孤立点也要一条边)。由于原无向图的四条边都存在,我们最后得出的最少的连接方式一定是可行的,而这种最少的连接数目,与最小边覆盖是相等的,原理上也很相近。

下面只要根据原图建立 G,求出 G 的最小边覆盖,问题就解决了。如何建立二分图?假设原图中 1 和 2 有连线:

建二分图时,将结点按照出现的顺序编号,由此建立结点之间的二分图,具体反映到邻接矩阵上是: has [1][2] = 1 同时 has [2][1] = 1。实际上将两个原图放在一起就是 G 。如果一定要讲出道理,按照上文匈牙利算法链接的思路,由于是无向图,那么既可以将男生当作名花无主,也可以将女生当作名花无主,在匹配关系上两者是平等的,也就是在二分图的同一个集合中两者都要出现。这和 POJ 3041http://blog.csdn.net/curious_again/article/details/72793496 不同,那题如果标记对称位置,则违背了图的含义。建好后的效果如下:

由于 G 是原图的双倍。那么 G 的最小边覆盖,当然等于原图的最小边覆盖的两倍。求 G 的最小边覆盖,只要用匈牙利算法,求出 G 的最大匹配数即可。


本题到这里就结束了。我还没看过定理二分图最小边覆盖 = 顶点数 - 最大匹配数的严格证明,我自己是这样理解的:

因为求的是最小边覆盖,先假定每个结点都连一条线段,表示所有的结点都被覆盖了。这时线段的数量 = 节点数


这个二分图的一个最小边覆盖是 1 连 2,3 连 4。一种匹配其实就是一种连接方式,将两个结点各自的线段合并成了一条线段,每有一个匹配边,线段的数量就减 2 增 1,也就是减少一条。可以发现:最少的线段的数量 = 节点数 - 最大匹配数,或者说二分图最小边覆盖 = 顶点数 - 最大匹配数。我也感觉其实这个定理前二分的限制可以去掉,对任意一个图这个都是成立的。

#include <iostream>#include <memory.h>#include <cstdio>#define SIZE 42 * 12#define ROW 45#define COL 11using namespace std;int has[SIZE][SIZE];//两个城市之间建立二分图int num[ROW][COL];//城市的标号int h, w;int dx[] = { -1,0,1,0 }, dy[] = { 0,1,0,-1 };int v1 = SIZE, v2 = SIZE, ans = 0;//v1, v2搜索集合时的边界int link[SIZE];//v1中谁配的v2, v2中的点做为下标访问linkbool vis[SIZE];//是否尝试过修改v2中的匹配void mark(int x, int y) {int n_x, n_y;for (int i = 0;i < 4;++i) {n_x = x + dx[i];n_y = y + dy[i];int n1 = num[x][y], n2 = num[n_x][n_y];//n1和n2分别是两个城市的编号if (0 <= n_x&&n_x < h && 0 <= n_y&&n_y < w && num[n_x][n_y]) {has[n1][n2] = 1;//城市之间的连接是双向的has[n2][n1] = 1;//这和 POJ 3041 不同, 如果只标记一边, 会漏掉可能的匹配}}}bool hgr(int x) {for (int y = 0;y < v2;++y) {if (has[x][y] && !vis[y]) {//每个y每次搜索调整一次vis[y] = 1;if (link[y] == 0 || hgr(link[y])) {link[y] = x;return true;}}}return false;}void solve() {for (int x = 0;x < v1;++x) {memset(vis, 0, sizeof(vis));if (hgr(x))++ans;}}int main() {//freopen("input.txt", "r", stdin);//养成用freopen的规范int cnt = 0;int n;cin >> n;char c;for (int k = 0;k < n;++k) {ans = 0;cnt = 0;memset(has, 0, sizeof(has));memset(num, 0, sizeof(num));cin >> h >> w;for (int i = 0;i < h;++i)//输入矩阵for (int j = 0;j < w;++j) {cin >> c;if (c == '*')num[i][j] = ++cnt;//num同时记录该位置是城市还是空地}//num按照第几个城市记录for (int i = 0;i < h;++i)for (int j = 0;j < w;++j)if (num[i][j]) {mark(i, j);}memset(link, 0, sizeof(link));solve();cout << cnt - ans / 2 << "\n";//公式, 这里相当于结果除2}return 0;}


阅读全文
0 0
原创粉丝点击