Kuhn-Munkers算法思想
- 设立左右顶标l,规定l(u)+l(v)>=w(u,v),相等子图是包含满足l(u)+l(v)=w(u,v)所有边的生成子图,相等子图的完美匹配是原图的最大权完美匹配。通过调整顶标,贪心地将边权最大的边变成相等边,逐渐扩大相等子图。
算法流程
- 初始化顶标。(一般将左顶标设为最大边权)
- 用匈牙利算法寻找可行匹配。
- 若未找到则修改顶标。
- 重复以上过程直至相等子图完备。
最大权完美匹配模板
- 通过状态延续使时间复杂度降至n3。
- vx,vy为单次标记的左右部的点,px,py分别为左右部各点的匹配点,模板中左右部的点分别从1~n编号,slack[i]表示右部i点的最小松弛量。
- 要根据题意初始化边权,有负权边应初始化为-inf,否则一般为0。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define int long long
using namespace std;
const int maxn = 505;
const int inf = 1e18;
int e[maxn][maxn],n,m,vx[maxn],vy[maxn],px[maxn],py[maxn],slack[maxn];
int lx[maxn],ly[maxn],pre[maxn];
void aug(int x)
{
int t;
while(x)
{
t = px[pre[x]];
py[x] = pre[x];
px[pre[x]] = x;
x = t;
}
}
void bfs(int x)
{
memset(vx,0,sizeof(vx));
memset(vy,0,sizeof(vy));
for(int i=1;i<=n;i++)
slack[i] = inf;
queue<int>q;
q.push(x);
while(1)
{
while(!q.empty())
{
int u=q.front();
q.pop();
vx[u] = 1;
for(int i=1;i<=n;i++)if(!vy[i])
{
if(lx[u]+ly[i]-e[u][i]<slack[i])
{
pre[i] = u;
slack[i] = lx[u]+ly[i]-e[u][i];
if(!slack[i])
{
vy[i] = 1;
if(!py[i])
{
aug(i);
return ;
}
else
q.push(py[i]);
}
}
}
}
int d = inf;
for(int i=1;i<=n;i++)
if(!vy[i])
d=min(d,slack[i]);
for(int i=1;i<=n;i++)
{
if(vx[i]) lx[i]-=d;
if(vy[i]) ly[i]+=d;
else slack[i]-=d;
}
for(int i=1;i<=n;i++)if(!vy[i])
{
if(!slack[i])
{
vy[i] = 1;
if(!py[i])
{
aug(i);
return ;
}
q.push(py[i]);
}
}
}
}
signed main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
lx[i] = -inf;
for(int j=1;j<=n;j++)
e[i][j]=-inf;
}
for(int i=1,u,v,w;i<=m;i++)
{
scanf("%lld%lld%lld",&u,&v,&w);
e[u][v]=max(e[u][v],w);
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
lx[i]=max(lx[i],e[i][j]);
}
for(int i=1;i<=n;i++)
bfs(i);
long long ans = 0;
for(int i=1;i<=n;i++)
ans+=e[py[i]][i];
cout<<ans<<endl;
for(int i=1;i<=n;i++)
cout<<py[i]<<' ';
return 0;
}
KM算法其它应用
- 最大权匹配,用0权边补全二分图跑KM算法,KM算法的使用前提是必须存在完美匹配。
- 最小权完美匹配,所有边权取相反数。
- 判断是否存在完美匹配,用-inf补全二分图,最后遍历匹配结果看是否使用-inf的边。
- 边权积的完美匹配,取自然对数,答案为eans。