您的位置:首页 > 其它

匈牙利算法 & [CodeVS 1222] 信与信封问题

2016-06-05 22:55 169 查看

匈牙利算法

匈牙利算法解决二分图最大匹配问题。有些地方说KM算法和匈牙利算法互为别称,但好像普遍称其中解决二分图最大匹配的那个算法为匈牙利,解决二分图最佳完美匹配的那个叫KM。

匈牙利算法的核心是寻找交互路径(增广路),即一条从未盖点出发,依次经过非匹配边、匹配边、非匹配边、匹配边……终止于未盖点的路径。说白了,寻找交互路径即寻找一对能互达的未盖点,限制是只能交替走非匹配边、匹配边。交互路径用于改进匹配,其中非匹配边的数量比匹配边多1,它们互换身份,就得到一个匹配数+1的匹配。因为如果这个过程不会使已盖点变成未盖点,并且X集合中只能使出发点变成已盖点,所以从前往后扫一遍就可以了。存在增广路=>不是最大匹配。还可以参考Matrix67的解读

不是最大匹配=>存在增广路。那么对于某未盖点x0,存在一对(x0, y0),它们相匹配得到更大的匹配。(x0, y0)之间存在一条未匹配边,(y0, x1)之间是一条匹配边,为了改进匹配,它们必须互换身份。按照那个存在的更大的匹配,未盖点x1寻找着配偶,调整……这个过程必定会终结,即(xn, yn)匹配后,不会有新的未盖点产生。而这个调整的过程,走的就是一条交互路径。

交互路径和网络流的增广路很像,走匹配边,就像走反向弧。

可以用dfs或bfs寻找交互路径,这里给出dfs的实现:

// MAXV:顶点总数的最大值,N:X集合的顶点数,E:邻接表的边数组,fst:邻接表头
#include <cstring>
namespace Hungary {
int left[MAXV+1], vis[MAXV+1], cnt;

bool cross_path(int x)
{
for (int i = fst[x]; i; i = E[i].next) {
int y = E[i].v;
if (vis[y] != cnt) {
vis[y] = cnt;
if (!left[y] || cross_path(left[y])) {
left[y] = x;
return true;
}
}
}
return false;
}

int Hungary()
{
memset(left, 0, sizeof(left));
memset(vis, 0, sizeof(vis));
int a = 0;
for (cnt = 1; cnt <= N; ++cnt)
if (cross_path(cnt))
++a;
return a;
}
};


添加计数器cnt,使得vis数组不用每次增广完都清零。

有一道模板题 POJ 1274。源于USACO。CodeVS上也有,但数据有误。

总是WA。比对自己和别人的实现,发现我的vis标记的是边,大家标记的是顶点,就像平常的dfs。改了就AC了。用USACO的数据对拍(下载到的原数据有误,CodeVS似乎就是用的这些),发现出错在后三组较大的数据,比正确答案小一点。于是自己生成数据。那是一节文科大课,我在机房里生成着随机数。得到了一组N=M=10的好数据,还没来得及看就……停电了……

中午又随机了好一会儿,得到一组N=M=10的数据。无奈规模还是太大,手动模拟不清。

天昏地暗的几天……

终于想到,

1. 我只标记了那些未匹配边,于是导致有些边会重复走。

2. 标记边的效率不会比标记点高。

如果把寻找交互路径,看作在走交互路径的基础上寻找一对未盖点,标记点也是更自然的。

信与信封问题

这个问题有意思。

John先生晚上写了n封信,并相应地写了n个信封将信装好,准备寄出。但是,第二天John的儿子Small

John将这n封信都拿出了信封。不幸的是,Small John无法将拿出的信正确地装回信封中了。

将Small John所提供的n封信依次编号为1,2,…,n;且n个信封也依次编号为1,2,…,n。假定Small

John能提供一组信息:第i封信肯定不是装在信封j中。请编程帮助Small John,尽可能多地将信正确地装回信封。

将“第i封信肯定不是装在信封j”取反,建立二分图,“第i封信可能装在信封j”。开始在想,是不是非得有一封只和一个信封相连的信,然后引发一系列连锁反应。写了一个程序,想了组数据,和题解比对了一下,不对。

都说它是匈牙利算法的变形。怎么变呢?匈牙利算法求最大匹配。这道题的最大匹配是完美匹配,求的是哪些匹配边是确定的。一条匹配边是确定的,也就是说,它无可取代。取代了会发生什么?引发矛盾,使得不合题意。这道题隐含的约束是什么?信和信封能建立一一对应。

如果一条匹配边是确定的,它必然包含在所有最大匹配——在本题中即完美匹配——里。随意地求出一组最大匹配,依次删去匹配边、再求最大匹配,如果仍是完美匹配,则此边可有可无;如果不是完美匹配,则此边不可或缺。

删去那条匹配边,其他(2n-2)个点仍处于匹配状态。只须从这条前匹配边的某端点出发找交互路径即可。

不知怎么的,总觉得必须保存初始的完美匹配,于是v1是这样的:

for (int i = 1; i <= n; ++i, ++tm)
dfs(i);

memcpy(save+1, left+1, sizeof(int)*n);

bool ok = false;
for (int y = 1; y <= n; ++y, ++tm) {
int x = left[y];
G[x][y] = false;
left[y] = 0;

if (!dfs(x)) {
envlp[x] = y;
ok = true;
}

G[x][y] = true;
memcpy(left+1, save+1, sizeof(int)*n);
}


想到干脆dfs里只找路,不进行修改了。v2是这样的:

for (int i = 1; i <= n; ++i, ++tm)
dfs(i, true);

bool ok = false;
for (int x = 1; x <= n; ++x, ++tm) {
int y = right[x];
G[x][y] = false;
right[x] = left[y] = 0;

if (!dfs(x, false)) {
printf("%d %d\n", x, y);
ok = true;
}

G[x][y] = true;
left[y] = x;
right[x] = y;
}


看了题解的代码,v3是这样的:

for (int i = 1; i <= n; ++i, ++tm)
dfs(i);

bool ok = false;
for (int x = 1; x <= n; ++x, ++tm) {
int y = right[x];
G[x][y] = false;
right[x] = left[y] = 0;
if (!dfs(x)) {
printf("%d %d\n", x, y);
ok = true;
left[y] = x;
right[x] = y;
}
G[x][y] = true;
}


修改了也无所谓,I don’t care~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: