[模板] + [详解] - 二分图 - KM算法

前言:

  建议在学KM之前先把二分图最大匹配的匈牙利算法学了,自己的传送门:传送门

讲解参考:

  我就不再贴一遍了,直接传送门吧:传送门


模板:

  加了一些自己的理解。

成员:

int love[maxn][maxn],n;//love[i][j]表示第i个女生对第j个男生的好感度
int ex_girl[maxn];//第i个女生的当前期望值为ex_girl[i]
int ex_boy[maxn];//同上
bool vis_girl[maxn];//标记在当前配对过程中girl[i]有没有被访问过
bool vis_boy[maxn];//同上
int match[maxn];//match[boy] = girl,说明这个boy和girl已经配对了,没有则为-1
int slack[maxn];//记录每个男生如果能被妹子倾心最少还需要多少期望值

DFS找增广匹配:

bool dfs(int girl) {
    vis_girl[girl] = true;
    for(int boy = 0 ; boy < n ; boy++) {
        if(vis_boy[boy]) continue;//每一轮匹配,每个男生只尝试一次
        int gap = ex_girl[girl] + ex_boy[boy] - love[girl][boy];
        if(gap == 0) {//如果符合要求
            vis_boy[boy] = true;
            if(match[boy] == -1 || dfs(match[boy])) {
                //找到一个没有配对的男生 或者是 和这个男生配对的女生可以找到别人
                match[boy] = girl;
                return true;
            }
        } else {
            slack[boy] = min(slack[boy],gap);
            //可以理解为这个男生要能够得到配对的话最少还需要多少期望
        }
    }
    return false;
}

KM算法主体:

int KM() {
    clr(match, -1);//初始每个男生都没有匹配的女生
    clr(ex_boy, 0);//初始每个男生的期望值
    for(int i=0 ; i<n ; i++) {
        //每个女生的初始期望值是与她相连的男生最大好感度
        ex_girl[i] = love[i][0];
        for(int j=1 ; j<n ; j++) {
            ex_girl[i] = max(ex_girl[i], love[i][j]);
        }
    }
    for(int i=0 ; i<n ; i++) {
        //尝试为每一个女生解决归宿问题
        fill(slack, slack+n, INF);//初始化为最大,随后取最小值
        while(true) {
            //为每个女生解决归宿问题的方法是:
            //如果找不到男票就降低自己的期望值,直到找到为止
            clr(vis_girl,0);
            clr(vis_boy,0);
            if(dfs(i)) break;//找到归宿,退出循环
            //如果找不到,就降低妹子的期望值
            int d = INF;//能够降低的最小期望值derta
            for(int j = 0; j<n ; j++) {
                //遍历所有一点在匈牙利树中另一点不在匈牙利树种的边
                //slack[i]的值是girl[i] 所在边的 端点权值之和-边的权值
                //取它们中的最小值,就是最小可以松弛的期望值
                if(!vis_boy[j]) d = min(d, slack[j]);
            }
            for(int j = 0; j<n ; j++) {
                //对每个访问过的女生减少期望
                if(vis_girl[j]) ex_girl[j] -= d;
                //所有访问过的男生增加期望
                if(vis_boy[j]) ex_boy[j] += d;
                //所有没访问过的男生距离自己被配对又更近了一步
                else slack[j] -= d;
            }
        }
    }
    //配对完成,求出所有的好感度和
    int res = 0;
    for (int i = 0; i < n ; i++)
        res += love[match[i]][i];
    return res;
}

合体版:

int love[maxn][maxn],n;//love[i][j]表示第i个女生对第j个男生的好感度
int ex_girl[maxn];//第i个女生的当前期望值为ex_girl[i]
int ex_boy[maxn];//同上
bool vis_girl[maxn];//标记在当前配对过程中girl[i]有没有被访问过
bool vis_boy[maxn];//同上
int match[maxn];//match[boy] = girl,说明这个boy和girl已经配对了,没有则为-1
int slack[maxn];//记录每个男生如果能被妹子倾心最少还需要多少期望值

bool dfs(int girl) {
    vis_girl[girl] = true;
    for(int boy = 0 ; boy < n ; boy++) {
        if(vis_boy[boy]) continue;//每一轮匹配,每个男生只尝试一次
        int gap = ex_girl[girl] + ex_boy[boy] - love[girl][boy];
        if(gap == 0) {//如果符合要求
            vis_boy[boy] = true;
            if(match[boy] == -1 || dfs(match[boy])) {
                //找到一个没有配对的男生 或者是 这个男生可以找到别人
                match[boy] = girl;
                return true;
            }
        } else {
            slack[boy] = min(slack[boy],gap);
            //可以理解为这个男生要能够得到配对的话最少还需要多少期望
        }
    }
    return false;
}

int KM() {
    clr(match, -1);//初始每个男生都没有匹配的女生
    clr(ex_boy, 0);//初始每个男生的期望值
    for(int i=0 ; i<n ; i++) {
        //每个女生的初始期望值是与她相连的男生最大好感度
        ex_girl[i] = love[i][0];
        for(int j=1 ; j<n ; j++) {
            ex_girl[i] = max(ex_girl[i], love[i][j]);
        }
    }
    for(int i=0 ; i<n ; i++) {
        //尝试为每一个女生解决归宿问题
        fill(slack, slack+n, INF);//初始化为最大,随后取最小值
        while(true) {
            //为每个女生解决归宿问题的方法是:
            //如果找不到男票就降低自己的期望值,直到找到为止
            clr(vis_girl,0);
            clr(vis_boy,0);
            if(dfs(i)) break;//找到归宿,退出循环
            //如果找不到,就降低妹子的期望值
            int d = INF;//能够降低的最小期望值derta
            for(int j = 0; j<n ; j++) {
                //遍历所有一点在匈牙利树中另一点不在匈牙利树种的边
                //slack[i]的值是girl[i] 所在边的 端点权值之和-边的权值
                //取它们中的最小值,就是最小可以松弛的期望值
                if(!vis_boy[j]) d = min(d, slack[j]);
            }
            for(int j = 0; j<n ; j++) {
                //对每个访问过的女生减少期望
                if(vis_girl[j]) ex_girl[j] -= d;
                //所有访问过的男生增加期望
                if(vis_boy[j]) ex_boy[j] += d;
                //所有没访问过的男生距离自己被配对又更近了一步
                else slack[j] -= d;
            }
        }
    }
    //配对完成,求出所有的好感度和
    int res = 0;
    for (int i = 0; i < n ; i++)
        res += love[match[i]][i];
    return res;
}

例题:

HDU 2255
poj 3565
hdu 1533
hdu 1853
hdu 3488
hdu 3435
hdu 2426
hdu 2853
hdu 3718
hdu 3722
hdu 3395
hdu 2282
hdu 2813
hdu 2448
hdu 2236
hdu 3315
hdu 3523

算法复杂度:

  这个模板有优化slack的更新过程,复杂度应该是 O(n3) ,如果我没算错的话。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值