oi不需要证明,确实因为时间有限,还有脑力有限,咳咳~就直接给结论和算法流程了
二分图有两类点,这里称\(X\)类,和\(Y\)类。

  • 顶标:\(ex\)\(ey\),分别表示\(x\)类点和\(y\)类点的顶标。此算法全程任意边\((u,v)\)满足\(ex(u)+ex(v)\ge len(u,v)\)

  • 相等子图:由满足\(ex(u)+ey(v)=len(u,v)\)的边构成的子图。

  • 性质:相同子图存在原图的完美匹配,则一定是最大完美匹配

  • 目标:满足 \(ex(u)+ex(v)\ge len(u,v)\) 的情况下调整顶标,加入边到相等子图中找增广路,使得得到原图的完美匹配。

  • 算法流程:我们在匈牙利算法找增广路的背景下设计该算法。
    1.从\(X\)中每个点出发,找增广路。
    2.找到增广路则结束
    3.否则,通过改顶标加入一边。回到\(2\)
    流程很简单,不过怎么改顶标。
    首先明确要找的边从\(X\)已经遍历到的点连向\(Y\)还未遍历到的点。
    因此可以将遍历到的边 \(ex(u)-=\Delta\)\(ex(v)+=\Delta\),这样遍历到的边不会改变但满足上面的边会减少\(\Delta\)
    综上加入此类边中\(ex(u)+ex(v)-len(u,v)\)最小的边即可。
    而查询最小\(O(n^2)\)可以用slack优化至\(O(n)\)\(slack\)维护\(Y\)中未遍历到的每个点到所有\(X\)中遍历到的点的\(min(ex(u)+ex(v)-len(u,v))\)
    每次加边导致\(X\)中有新点被遍历,直接\(O(n)\)更新,查询\(slack\)扫一遍找新边也是\(O(n)\)的。
    流程结合代码看一下就好了,注意改顶标时别漏了起点(从\(0\)开始遍历)。
    由于每次是加入一条边,所以直接用bfs迭代的思想即可(写的也不像bfs)。
    看代码显然:\(O(n^3)\)

  • code

点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll inf = 1e18;
const int N = 505;

int pre[N], link[N], n;
ll ex[N], ey[N], mp[N][N], slack[N];
bool visy[N];

void KM() {
	for(int s = 1; s <= n; s++) {
		for(int i = 1; i <= n; i++) {slack[i] = inf; pre[i] = 0; visy[i] = 0;}
		int x, y = 0; link[y] = s;
		while(1) {
			x = link[y]; visy[y] = 1;
			ll dta = inf, ny;
			for(int i = 1; i <= n; i++) {
				if(visy[i]) continue;
				ll w = ex[x] + ey[i] - mp[x][i];
				if(slack[i] > w) {slack[i] = w; pre[i] = y;}
				if(dta > slack[i]) {dta = slack[i]; ny = i;}
			}
			for(int i = 0; i <= n; i++) {		//link0=s 
				if(visy[i]) {ex[link[i]] -= dta; ey[i] += dta;}
				else {slack[i] -= dta;} 
			}
			y = ny;
			if(!link[y]) {break;}
		}
		while(y) {link[y] = link[pre[y]]; y = pre[y];}
	}
	ll res = 0;
	for(int i = 1; i <= n; i++) res += mp[link[i]][i];
	printf("%lld\n", res);
	for(int i = 1; i <= n; i++) {printf("%d ", link[i]);}
}

int main() {
	int m; scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i++) {for(int j = 1; j <= n; j++) mp[i][j] = -inf; ex[i] = -inf;}
	for(int i = 1; i <= m; i++) {
		int u, v; ll w; scanf("%d%d%lld", &u, &v, &w);
		mp[u][v] = max(mp[u][v], w);
		ex[u] = max(ex[u], w);
	}
	KM();
	return 0;
}
  • 非完美匹配
    • 首先保证\(|X|\le |Y|\)
    • 一定要是完美匹配,首先\(|X|=|Y|\),虚边设\(-inf\)\(X\)中每个点出发如果\(min\)到的为\(-inf\),也就是无边那就无解。
    • 最大匹配就行:虚边都是\(0\)