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\)。