戳这里!——→KM算法详解
考虑到二分图中两个集合中的点并不总是相同,为了能应用 KM算法解决二分图的最大权匹配,需要先作如下处理:
将两个集合中点数比较少的补点,使得两边点数相同,再将不存在的边权重设为 0,这种情况下,问题就转换成求最大权完美匹配问题 ,从而能应用KM算法求解。
具体算法流程:
(1)分配可行顶标,对每个顶标执行(2)(3)(4)
(2)匈牙利算法找到增广路
(3)找不到增广路就调整顶标。
(4)重复(2),(3)直到找到增广路
DFS版本:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 507, M = 5e3 +7, maxn = 500;
const int mod = 1e9+7;
const ll INF = 1e18+7;
ll W[maxn][maxn],n, m;
ll Lx[maxn],Ly[maxn];//顶标
int Left[maxn];//右边第i个点对应的左边的点的编号
bool S[maxn],T[maxn];//是否在增广路
bool match(int i){
S[i]=true;
for(int j=1;j<=n;j++)
if(Lx[i]+Ly[j]==W[i][j] && !T[j]){//i到j可行 且 j未被访问
T[j]=true;
if(!Left[j] || match(Left[j])){ //j未标记 或者 通过j可以找打增广路
Left[j]=i;
return true;
}
}
return false;
}
void update(){//更新顶标
ll a=INF;
for(int i=1;i<=n;i++)if(S[i]){
for(int j=1;j<=n;j++)if(!T[j])a=min(a,Lx[i]+Ly[j]-W[i][j]);//i在增广路 且 j不在增广路中
}
for(int i=1;i<=n;i++){
if(S[i])Lx[i]-=a; //更新左边的顶标
if(T[i])Ly[i]+=a; //更新右边的顶标
}
}
ll KM(){
for(int i=1;i<=n;i++){
Left[i]=Lx[i]=Ly[i]=0;
for(int j=1;j<=n;j++)Lx[i]=max(Lx[i],W[i][j]);
}
//初始化左顶标和右顶标
for(int i=1;i<=n;i++){ //匹配每一个左端点
for(;;){
for(int j=1;j<=n;j++)S[j]=T[j]=0; //每次匹配前清空标记
if(match(i))break; //找到增广路就退出
else update(); //找不到增广路就更新顶标
}
}
ll ans = 0;
for(int i = 1; i <= n; ++ i) ans += W[Left[i]][i];
return ans;
}
void solve() {
scanf("%lld%lld", &n, &m);
for(int i = 1; i <= n; ++ i)
for(int j = 1; j <= n; ++ j)
W[i][j] = -INF; //如果带有负权边的话,不做初始化操作会使得
//两个没有边的点之间的边权值为0,会导致答
//案错误,所以如果两个点之间无边,我们把
//它设为负无穷
for(int i = 1; i <= m; ++ i) {
ll a, b, c;
scanf("%lld%lld%lld%", &a, &b, &c);
W[a][b] = max(W[a][b], c);
}
printf("%lld\n", KM());
for(int i = 1; i <= n; ++ i) printf("%d ", Left[i]);
}
int main() {
#ifndef ONLINE_JUDGE
freopen("cf.in", "r", stdin);
freopen("cf.out", "w", stdout);
#endif
// int t;
// cin >> t;
// while(t --) {
solve();
// }
return 0;
}
我们发现dfs版本的复杂度并不能A掉这个题,因为n是500,所以n^4已经超接近10s了,所以我们可以换bfs版本来试一试。
BFS版本:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 507, M = 5e3 +7, maxn = 1007;
const int mod = 1e9+7;
const ll INF = 1e15+7;
ll w[N][N];//边权
ll la[N], lb[N];//左、右部点的顶标
bool va[N], vb[N];//访问标记,是否在交错树中
int match[N];//右部点匹配的左部点(一个只能匹配一个嘛)
int n;
ll delta, upd[N];
int p[N];
ll c[N];
void bfs(int x) {
int a, y = 0, y1 = 0;
for(int i = 1; i <= n; ++ i)
p[i] = 0, c[i] = INF;
match[y] = x;
do{
a = match[y], delta = INF, vb[y] = true;
for(int b = 1; b <= n; ++ b){
if(!vb[b]){
if(c[b] > la[a] + lb[b] - w[a][b])
c[b] = la[a] + lb[b] - w[a][b], p[b] = y;
if(c[b] < delta)//Δ还是取最小的
delta = c[b], y1 = b;
}
}
for(int b = 0; b <= n; ++ b)
if(vb[b])
la[match[b]] -= delta, lb[b] += delta;
else c[b] -= delta;
y = y1;
}while(match[y]);
while(y)match[y] = match[p[y]], y = p[y];
}
ll KM() {
for(int i = 1; i <= n; ++ i)
match[i] = la[i] = lb[i] = 0;
for(int i = 1; i <= n; ++ i){
for(int j = 1; j <= n; ++ j)
vb[j] = false;
bfs(i);
}
ll res = 0;
for(int y = 1; y <= n; ++ y)
res += w[match[y]][y];
return res;
}
int m;
void solve() {
scanf("%lld%lld", &n, &m);
for(int i = 1; i <= n; ++ i)
for(int j = 1; j <= n; ++ j)
w[i][j] = -INF; //如果带有负权边的话,不做初始化操作会使得
//两个没有边的点之间的边权值为0,会导致答
//案错误,所以如果两个点之间无边,我们把
//它设为负无穷
for(int i = 1; i <= m; ++ i) {
ll a, b, c;
scanf("%lld%lld%lld%", &a, &b, &c);
w[a][b] = max(w[a][b], c);
}
printf("%lld\n", KM());
for(int i = 1; i <= n; ++ i) printf("%d ", match[i]);
}
int main() {
#ifndef ONLINE_JUDGE
freopen("cf.in", "r", stdin);
freopen("cf.out", "w", stdout);
#endif
// int t;
// cin >> t;
// while(t --) {
solve();
// }
return 0;
}