KM(带权最大二分匹配) 模板

板子:
找完全二分图的完美匹配
模板题 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
  • 边权之积最大,取对数(对数相加 就等于指数相乘嘛)

关于标杆: 如果是求最大权匹配, hl[i] + hr[i] >= a[i][j], 这个是基本满足条件, 事实上我们找的是可行边, 也就是hl[i] + hr[i] == a[i][j], 所以最后a矩阵的匹配和也一定等于标杆之和, 因为a数组是原图的权值又是最大匹配, 标杆又有这个不等式关系, 所以此时的标杆之和是最小的.

比如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);

同理, 如果要求标杆之和尽量的大, 那么满足的条件一定是hl[i] + hr[i] <= a[i][j], 那么此时我们要求的就是最小权值匹配了, 将边权取反, 然后跑KM, 最后的标杆之和就是最大的.

比如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());
    }
}

你可能感兴趣的:(km匹配,板子)