传送门:奔小康赚大钱
裸的 KM 算法,上板子。
#include <bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 3e2+32;
int n, nx, ny;
int g[maxn][maxn];
int linker[maxn], lx[maxn], ly[maxn]; //y点中的匹配状态,xy的杠杆值
int slack[maxn]; //新加边的最小差值
bool visx[maxn], visy[maxn];
void read()
{
nx = ny = n;
for(int i = 0; i < nx; ++i)
for(int j = 0; j < ny; ++j)
cin >> g[i][j];
}
bool dfs(int x)
{
visx[x] = 1;
for(int i = 0; i < ny; ++i)
{
if(visy[i]) //每一轮匹配 右图每个点只访问一次
continue;
int t = lx[x] + ly[i] - g[x][i]; //差值
if(!t)
{
visy[i] = 1;
if(linker[i] == -1 || dfs(linker[i])) //找到一个未匹配的点,形成增广路
{
linker[i] = x;
return 1;
}
}
else
slack[i] = min(slack[i], t); //更新最小差值
}
return 0;
}
int KM()
{
memset(linker, -1, sizeof(linker)); //无匹配
memset(ly, 0, sizeof(ly)); //右图各点期望值为0
for(int i = 0; i < nx; ++i)
{
lx[i] = -INF;
for(int j = 0; j < ny; ++j)
lx[i] = max(lx[i], g[i][j]); //lx设置为连接的边权的最大值,即期望的最大值
}
for(int i = 0; i < nx; ++i)
{
memset(slack, INF, sizeof(slack));
while(1)
{
memset(visx, 0, sizeof(visx));
memset(visy, 0, sizeof(visy));
if(dfs(i)) //形成了增广路,匹配成功
break;
//否则,新加边使得能够匹配
int d = INF; //计算d值,d为当前加上一条边,左图需要减少的最小的期望值
for(int j = 0; j < ny; ++j)
if(!visy[j])
d = min(d, slack[j]); //求得最小值
for(int j = 0; j < nx; ++j)
if(visx[j]) //在访问过的点中求
lx[j] -= d; //左图减去最小的期望差值
for(int j = 0; j < ny; ++j)
{
if(visy[j]) //在访问过的点中求
ly[j] += d; //右图加上最小的期望差值
else
slack[j] -= d; //左图期望降低了,那么差值就变小了
}
}
}
int ret = 0;
for(int i = 0; i < ny; ++i)
if(~linker[i]) //有匹配
ret += g[linker[i]][i]; //加上权值
return ret;
}
void solve()
{
cout << KM() << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
while(cin >> n)
{
read();
solve();
}
return 0;
}