zoukankan      html  css  js  c++  java
  • KM算法学习小记:

    KM算法用于解决二分图最大权匹配问题,这个问题应该是可以用费用流就解决的。

    近期遇到了用KM算法去解不等式的题,虽然转换完后还是可以用费用流做,学习中感觉到顶标挺有用的。

    学习自:

    https://blog.csdn.net/c20180630/article/details/71080521

    https://www.cnblogs.com/huyufeifei/p/10350763.html


    假设我们解决的是最大权完全匹配问题,非完全匹配之后再讨论怎么做。

    (a[i][j])为左边第i个点到右边第j个点的最大边的权值,如果没有就是-inf。

    一开始,对左边的点,定义定标(hl[x]),初值为(x)的出边的最大权值,对右边的点,也定义定标(hr[y]),初值为0。

    当前的相等子图的定义是:只保留(hl[x]+hr[y]=a[x][y])的边。

    算法本质上和匈牙利算法类似,需要为每一个点都找到一个匹配点。

    所以流程如下:
    枚举左半部分的每一个点x,利用匈牙利算法尝试在当前的相等子图中为x找到一个匹配。

    若不能,则找到一个最小的权值D,把已遍历的左边的点的hl-=D,已遍历的右边的点的hr-=D,可以发现左边的点一定比右边的多1(因为x没有匹配点),这样总权值-=D,实现了最小的扩张。

    这个权值D就是那些不合法的边中的最小的(hl[x]+hr[y]-a[x][y])

    直接这样写复杂度会被卡到(O(n^4))

    http://uoj.ac/problem/80 这题并过不了。

    Code(DFS):

    #include<bits/stdc++.h>
    #define fo(i, x, y) for(int i = x, _b = y; i <= _b; i ++)
    #define ff(i, x, y) for(int i = x, _b = y; i <  _b; i ++)
    #define fd(i, x, y) for(int i = x, _b = y; i >= _b; i --)
    #define ll long long
    #define pp printf
    #define hh pp("
    ")
    using namespace std;
    
    const int N = 405;
    
    int nl, nr, m, x, y, z;
    int a[N][N];
    
    const int inf = 1e9;
    
    int hl[N], hr[N], vl[N], vr[N], chox[N], choy[N];
    int mi;
    
    int find(int x) {
    	vl[x] = 1;
    	fo(y, 1, nr) {
    		if(vr[y]) continue;
    		int t = hl[x] + hr[y] - a[x][y];
    		if(!t) {
    			vr[y] = 1;
    			if(!choy[y] || find(choy[y])) {
    				chox[x] = y; choy[y] = x;
    				return 1;
    			}
    		} else mi = min(mi, t);
    	}
    	return 0;
    }
    
    int main() {
    	scanf("%d %d %d", &nl, &nr, &m);
    	fo(i, 1, m) {
    		scanf("%d %d %d", &x, &y, &z);
    		a[x][y] += z;
    	}
    	fo(i, 1, nl) fo(j, 1, nr) hl[i] = max(hl[i], a[i][j]);
    	int Nr = nr; nr = max(nr, nl);
    	fo(i, 1, nl) {
    		while(1) {
    			memset(vl, 0, sizeof vl);
    			memset(vr, 0, sizeof vr);
    			mi = inf;
    			if(find(i)) break;
    			fo(j, 1, nl) if(vl[j]) hl[j] -= mi;
    			fo(j, 1, nr) if(vr[j]) hr[j] += mi;
    		}
    	}
    	ll ans = 0;
    	fo(i, 1, nl) ans += hl[i];
    	fo(i ,1, nr) ans += hr[i];
    	pp("%lld
    ", ans);
    	fo(i, 1, nl) pp("%d ", a[i][chox[i]] ? chox[i] : 0);
    }
    

    然后在网上发现还有一种BFS写法,我理解了好久,虽然本质上没有区别。

    Code(BFS):

    #include<bits/stdc++.h>
    #define fo(i, x, y) for(int i = x, _b = y; i <= _b; i ++)
    #define ff(i, x, y) for(int i = x, _b = y; i <  _b; i ++)
    #define fd(i, x, y) for(int i = x, _b = y; i >= _b; i --)
    #define ll long long
    #define pp printf
    #define hh pp("
    ")
    using namespace std;
    
    const int N = 405;
    
    int nl, nr, m, x, y, z;
    int a[N][N];
    
    const int inf = 1e9;
    
    int hl[N], hr[N], vl[N], vr[N], chox[N], choy[N], sla[N], pre[N];
    int mi;
    
    void bfs(int x) {
    	memset(sla, 127, sizeof sla);
    	memset(pre, 0, sizeof pre);
    	memset(vl, 0, sizeof vl);
    	memset(vr, 0, sizeof vr);
    	int u = 0, nu;
    	choy[u] = x;
    	do {
    		x = choy[u];
    		ll D = 1e9;
    		vr[u] = 1;
    		fo(y, 1, nr) if(!vr[y]) {
    			ll t = hl[x] + hr[y] - a[x][y];
    			if(t < sla[y]) {
    				sla[y] = t;
    				pre[y] = u;
    			}
    			if(sla[y] < D) {
    				D = sla[y], nu = y;
    			}
    		}
    		hl[choy[0]] -= D; hr[0] += D;
    		fo(i, 1, nr) {
    			if(vr[i]) {
    				hl[choy[i]] -= D, hr[i] += D;
    			} else sla[i] -= D;
    		}
    		u = nu;
    	} while(choy[u]);
    	for(; u; u = pre[u])
    		choy[u] = choy[pre[u]];
    }
    
    int main() {
    	scanf("%d %d %d", &nl, &nr, &m);
    	fo(i, 1, m) {
    		scanf("%d %d %d", &x, &y, &z);
    		a[x][y] += z;
    	}
    	fo(i, 1, nl) fo(j, 1, nr) hl[i] = max(hl[i], a[i][j]);
    	nr = max(nr, nl);
    	fo(i, 1, nl) bfs(i);
    	ll ans = 0;
    	fo(i, 1, nl) ans += hl[i];
    	fo(i, 1, nr) ans += hr[i];
    	fo(i, 1, nr) chox[choy[i]] = i;
    	pp("%lld
    ", ans);
    	fo(i, 1, nl) pp("%d ", a[i][chox[i]] ? chox[i] : 0);
    }
    

    问题1:

    不完全匹配怎么做?

    方法:

    (a[x][y])若没有边,则(a[x][y]=0),且如果左边点比右边点多,则右边要补一些空点,这样当一个点匹配和它没有边的点时,相当于不选。

    问题2:
    bfs的写法只能用邻接矩阵存边,就是两两点之间一定要有条边,不然UOJ那题的样例就会挂。

    这是因为bfs写法的特殊性,读者可以自行理解其中的奥妙(bfs的顺序出来的不一定是最优的增广路)。

    问题3:

    开头说到的解不等式,下面这题:

    https://ac.nowcoder.com/acm/contest/4010/I

    (x[i]+y[j]>=a[i][j]>=0)

    (sum x+sum y)的最小值。

    直接做KM,最后剩下的顶标就是答案,顶标和就是最大权匹配,所以可以直接费用流。

    我的提交:

    https://ac.nowcoder.com/acm/contest/view-submission?submissionId=42982840

  • 相关阅读:
    DevExpress.XtraScheduler控件的使用方法
    读写Excel文档
    让程序以管理员身份运行
    读写TXT文档
    判断程序是自动启动还是用户启动
    用指针读BMP图像
    判断网络是否连接通
    WSL初体验
    Realsense内参标定
    FreeSwitch权威指南
  • 原文地址:https://www.cnblogs.com/coldchair/p/12309736.html
Copyright © 2011-2022 走看看