板子:
找完全二分图的完美匹配
模板题 HDU - 2255
PS: 之前dfs + hungry 算法不够优秀, 跑某些题T了, 所以换这个板子, 虽然不太懂原理, 但是跑的很快啊.
const int maxn = 205;
const ll INF = 1e18;
const db eps = 1e-6;
bool sgn(db x) { // db时要用
return fabs(x)>eps;
}
struct Matrix {
int n;
ll a[maxn][maxn];
};
struct KuhnMunKres:Matrix { // 继承
ll hl[maxn], hr[maxn], slk[maxn]; // 两边的标杆和slk优化
int fl[maxn], fr[maxn], vl[maxn], vr[maxn], pre[maxn];
// fl[i] 表示匹配后左边i对应的右边的点, vl表示左边点的标记
// 其他同理, 如果没有匹配则是-1.
int q[maxn], ql, qr; // 模拟队列
int check(int i) {
if (vl[i] = 1, fl[i] != -1) return vr[q[qr++]=fl[i]] = 1;
while(i != -1) swap(i, fr[fl[i] = pre[i]]);
return 0;
}
void bfs(int st) {
for (int i = 1 ; i <= n ; i ++) {
slk[i] = INF; vl[i] = vr[i] = 0;
}
q[ql = 0] = st;
vr[st] = qr = 1;
for (ll d;;) {
while(ql < qr) {
for (int i = 1, j = q[ql++]; i <= n ; i ++)
if (d = hl[i] + hr[j] - a[i][j], !vl[i] && slk[i] >= d)
if (pre[i] = j, d) slk[i] = d;
else if (!check(i)) return ;
}
d = INF;
for (int i = 1 ; i <= n ; i ++) {
if (!vl[i] && d > slk[i]) d = slk[i];
}
for (int i = 1 ; i <= n ; i ++) {
if (vl[i]) hl[i] += d;
else slk[i] -= d;
if (vr[i]) hr[i] -= d;
}
for (int i = 1 ; i <= n ; i ++) {
if (!vl[i] && !slk[i] && !check(i)) return ;
} // db判断slk时用上面那个函数.
}
}
void ask() {
for (int i = 1 ; i <= n ; i ++) {
fl[i] = fr[i] = -1; hr[i] = 0;
}
for (int i = 1 ; i <= n ; i ++) hl[i] = *max_element(a[i]+1, a[i]+1+n);
for (int i = 1 ; i <= n ; i ++) bfs(i);
}
}km;
void solve() {
scanf("%d", &km.n);
for (int i = 1 ; i <= km.n ; i ++) {
for (int j = 1 ; j <= km.n ; j ++) {
scanf("%lld", &km.a[i][j]);
// km.a[i][j] *= -1;
// 求最小匹配时用, 或者标杆之和尽量的大时
}
}
km.ask();
ll ans = 0;
// for (int i = 1 ; i <= km.n ; i ++) {
// ans -= km.hl[i] + km.hr[i];
// }
for (int i = 1 ; i <= km.n ; i ++) {
ans += km.a[km.fr[i]][i];
}
// for (int i = 1 ; i <= km.n ; i ++) {
// ans -= km.a[i][km.fl[i]];
// }
printf("%lld\n", ans);
}
//这个模型还是比较好看出来, 就是建图时要多想想!
记一下几种转换 : (好好想想)
- 最小权值匹配, 取反 (所有的边权取反,即取成负的边,最后答案再添个负号就行了,其他步骤一样)
- 没有完备匹配,赋0
- 边权之积最大,取对数(对数相加 就等于指数相乘嘛)
比如Uva - 11383 就是上面这种情况, 直接跑Km, 输出最后的ans即可.
关键代码:
ll ans = 0;
for (int i = 1 ; i <= km.n ; i ++) {
printf("%lld%c", km.hl[i], " \n"[i==km.n]);
ans += km.hl[i];
}
for (int i = 1 ; i <= km.n ; i ++) {
printf("%lld%c", km.hr[i], " \n"[i==km.n]);
ans += km.hr[i];
}
printf("%lld\n", ans);
比如HDU - 6346, 这个就是上面的情况, 将权值取反后跑KM, 然后累加答案即可. 注意这道题卡一般的板子, 所以也是一道测板子的好题, 不过用我的板子可以过… xixi
关键代码:
void solve() {
scanf("%d", &km.n);
for (int i = 1 ; i <= km.n ; i ++) {
for (int j = 1 ; j <= km.n ; j ++) {
scanf("%lld", &km.a[i][j]);
km.a[i][j] *= -1; // 这个转化!!!
}
}
km.ask();
ll ans = 0;
for (int i = 1 ; i <= km.n ; i ++) {
ans -= km.a[km.fr[i]][i];
}
printf("%lld\n", ans);
}
因为KM本来就是找完全二分图的最佳匹配, 然后对于上面那个板子要求一定是方阵, 我改了好久改成矩阵的, 发现不行, 主要是不懂这个板子的实现原理….. 对于某些题是矩阵的匹配就用不了….. 所以还是存一个之前的板子, 虽然慢了点但是有适用的地方.
const int maxn = 305;
const int inf = 0x3f3f3f3f;
int g[maxn][maxn];
int link[maxn], lx[maxn], ly[maxn];
bool visx[maxn], visy[maxn];
int nx,ny,d;
bool Find(int x) {
visx[x] = true;
for(int i = 1 ; i <= ny ; i ++) {
if(visy[i]) continue;
int tmp = lx[x] + ly[i] - g[x][i];
if(!tmp) {
visy[i] = true;
if(link[i] == -1 || Find(link[i])) {
link[i] = x;
return true;
}
}
else d = min(d, tmp);
}
return false;
}
int KM() {
for (int i = 1 ; i <= max(nx, ny) ; i ++) {
lx[i] = ly[i] = 0; link[i] = -1;
}
for(int i = 1 ; i <= nx ; i ++) {
for(int j = 1 ; j <= ny ; j ++) {
if(g[i][j] > lx[i])
lx[i] = g[i][j];
}
}
for(int i = 1 ; i <= nx ; i ++) {
while(1){
for (int i = 1 ; i <= max(nx, ny) ; i ++)
visx[i] = visy[i] = false;
d = inf;
if(Find(i)) break;
for(int j = 1 ; j <= nx ; j ++)
if(visx[j]) lx[j] -= d;
for(int j = 1 ; j <= ny ; j ++)
if(visy[j]) ly[j] += d;
}
}
int ans = 0;
for(int i = 1 ; i <= ny ; i ++) {
if(link[i] == -1) continue;
ans += g[link[i]][i];
}
return ans;
}
void solve() {
while(~scanf("%d",&nx)){
ny = nx;
for(int i = 1 ; i <= nx ; i ++) {
for(int j = 1 ;j <= ny ; j ++){
scanf("%d", &g[i][j]);
}
}
printf("%d\n", KM());
}
}