POJ 2400 KM+建图

POJ 2400

题意

  • n个部门n个员工,每个部门雇佣一个员工,部门希望雇佣每个员工的希望值不同,员工希望被雇佣的部门的希望值也不同, 求使得所有人希望值之和最大,输出最优匹配结果,若有多种,输出所有

思路(借鉴大佬)

  • 题目数据与描述不一样,先给出的每个员工的对部门的希望值,然后才是每个部门的首选项,

  • 将左部分作为部门,右部分作为员工,边权为各自希望值之和,KM求出最大的希望值之和

  • 要求输出的 最好平均差(通过样例理解)就是 (每个部门和员工都是最好匹配的权值和- 最大权值匹配和)/总个数

  • 得到最好平均差还要dfs搜二分图,把搜到的匹配和KM求出的ans比较, 相等则说明是一组最大权值匹配


  • 为什么w[][]还是负值? 不是求最大希望和吗? 因为希望最大的在最开始,那么换成w[to][i] = n-j等价于换成负数(体会一下)
#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;
const int inf = 1<<30;
const int maxn = 25;
int w[maxn][maxn],la[maxn],lb[maxn],match[maxn];
int n,m,delta,cnt,ans;
bool va[maxn],vb[maxn];

int dfs(int u) {
    va[u] = 1;
    for (int i=1; i<=n; ++i) {
        if (!vb[i]) {
            if (la[u]+lb[i]-w[u][i] == 0) {
                vb[i] = 1;
                if (match[i]==-1 || dfs(match[i])) {
                    match[i] = u;
                    return 1;
                }
            } else delta = min(delta,la[u]+lb[i]-w[u][i]);
        }
    }
    return 0;
}

int KM() {
    for (int i=0; i<maxn; ++i) la[i] = -inf;
    memset(lb,0,sizeof lb);
    memset(match,-1,sizeof match);
    for (int i=1; i<=n; ++i)
        for (int j=1; j<=n; ++j)
            la[i] = max(la[i],w[i][j]);
    for (int i=1; i<=n; ++i) {
        while (1) {
            memset(va,0,sizeof va);
            memset(vb,0,sizeof vb);
            delta = inf;
            if (dfs(i)) break;
            for (int j=1; j<=n; ++j) {
                if (va[j]) la[j] -= delta;
                if (vb[j]) lb[j] += delta;
            }
        }
    }
    ans = 0;
    for (int i=1; i<=n; ++i)
        if (match[i]) ans += w[match[i]][i];
    return -ans;
}

void dfs(int u,int sum) {
    if (sum < ans) return ;//剪枝,因为边权都为负数,当前和都小于ans,再加上负数不可能达到ans的值(这组全排列不是KM的匹配)
    if (u > n) {// 前n位搜出来了,判断搜出来的边权和是否=ans
        if (sum != ans) return ;
        printf("Best Pairing %d\n",cnt++);
        for (int i=1; i<=n; ++i)
            printf("Supervisor %d with Employee %d\n",i,match[i]);
    } else {
        for (int i=1; i<=n; ++i) {
            if (!va[i]) {
                va[i] = 1;
                match[u] = i;
                dfs(u+1,sum+w[u][i]);
                va[i] = 0;
            }
        }
    }

}

int main () {
    freopen("1.in","r",stdin);
    int times,case1=1; cin >> times;
    while (times--) {
        scanf("%d",&n);
        memset(w,0,sizeof w);
        int to;
        for (int i=1; i<=n; ++i)
            for (int j=1; j<=n; ++j) {
                scanf("%d",&to);
                w[to][i] -= (j-1);
            }
        for (int i=1; i<=n; ++i)
            for (int j=1; j<=n; ++j) {
                scanf("%d",&to);
                w[i][to] -= (j-1);
            }
        printf("Data Set %d, Best average difference: %.6lf\n",case1++,KM()/(2.0*n));
        memset(va,0,sizeof va);
        cnt = 1;
        dfs(1,0);
        printf("\n");
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值