【板子】KM(n3板子)

#include <bits/stdc++.h>
using namespace std;
#define ll long long
//算法使用
//w[maxn][maxn] 数组表示图的邻接矩阵  
//pop表示二分图的左右两边的点的数量  
//点从0到n-1  
//

const int maxn = 50;
const int inf = 0x3f3f3f3f;
int w[maxn][maxn], x[maxn], y[maxn];
int pre_x[maxn], pre_y[maxn], son_y[maxn], slack[maxn], par[maxn];
int lx, ly, pop;

void adjust(int v)
{
    son_y[v] = pre_y[v];
    if (pre_x[son_y[v]] != -2)
    {
        adjust(pre_x[son_y[v]]);
    }
}

bool find(int v)
{
    //遍历所有没有被匹配的y点寻找可能的增广路
    for (int i = 0; i < pop; i++)
    {
        if (pre_y[i] == -1)
        {
            //对于无法匹配的遍,记录一下“最少降低多少x的期望就可以和该y匹配”,该值就是slack
            //假设不计算slack,那么每次减一并不能保证减小后可以获得新的增广路
            if (slack[i] > x[v] + y[i] - w[v][i])
            {
                slack[i] = x[v] + y[i] - w[v][i];
                par[i] = v;
            }
            //说明可以向这个方向深搜
            if (x[v] + y[i] == w[v][i])
            {
                pre_y[i] = v;
                //对面的yi点没有匹配边
                if (son_y[i] == -1)
                {
                    adjust(i);
                    return true;
                }
                //如果yi对于的匹配边另一端的xson节点已经出现在了增广路上,这条增广路不可用,去找下一个y节点
                if (pre_x[son_y[i]] != -1)
                {
                    continue;
                }
                //如果yi对于的匹配边另一端的xson节点没有出现在增广路上,下一个可以去跑xson节点,同时把xson节点指向yi节点
                pre_x[son_y[i]] = i;
                //建立了xv-yi-xson的增广后,去查询xson的循环
                if (find(son_y[i]))
                    return true;
            }
        }
    }
    return false;
}

ll KM()
{
    //初始化
    for (int i = 0; i < pop; i++)
    {
        son_y[i] = -1;
        y[i] = 0;
    }
    //给每个i节点寻找期望x[i]
    for (int i = 0; i < pop; i++)
    {
        x[i] = 0;
        for (int j = 0; j < pop; j++)
        {
            x[i] = max(x[i], w[i][j]);
        }
    }
    bool flag;
    //pop遍循环,每次循环为xi节点寻找对应的匹配
    for (int i = 0; i < pop; i++)
    {
        //pop遍循环,对某些内容进行初始化,pre_x和pre_y是用来记录当前的增广路径的,分别指向在增广路径中该点之前的一个节点
        for (int j = 0; j < pop; j++)
        {
            pre_x[j] = pre_y[j] = -1;
            slack[j] = inf;
        }
        //设置xi节点为起点
        pre_x[i] = -2;
        //跑dfs找增广路
        if (find(i))
            continue;
        //找不到增广路,所以需要修改期望
        flag = false;
        while (!flag)
        {
            int m = inf;

            //slack[i]表示对于点yi,在尝试拓展但却因为期望和原因不能拓展的点当中,为了能够匹配,需要“期望降低值”的最小值
            //slack[i]==INF说明没有某个点尝试和yi连接并失败
            //关键,i需要尝试连边过,且被拒绝了
            //在多有yi中去一个slack值最小的,取更新x和y的期望,就可以让下一次寻找增广路出现新的增广路
            for (int j = 0; j < pop; j++)
                if (pre_y[j] == -1)
                    m = min(m, slack[j]);

            //根据是否在“失败”的增广路径上进行操作
            for (int j = 0; j < pop; j++)
            {
                if (pre_x[j] != -1)
                    x[j] -= m;
                if (pre_y[j] != -1)
                    y[j] += m;
                //对于没有在增广路上出现过的点yi,因为x都降低了m,那么对应的“期望降低值”就被完成了一部分,所以去掉一个m
                //对于出现在增广路上的点,不是slack数组的具体服务对象
                else
                    slack[j] -= m;
            }
            //循环找到slack降低m后为0的值,这些值就是在上一次find中一次都没有被匹配到的节点
            //没有降到0的就不用跑了
            for (int j = 0; j < pop; j++)
            {
                if (pre_y[j] == -1 && !slack[j])
                {
                    pre_y[j] = par[j];
                    if (son_y[j] == -1)
                    {
                        adjust(j);
                        flag = true;
                        break;
                    }
                    pre_x[son_y[j]] = j;
                    if (find(son_y[j]))
                    {
                        flag = true;
                        break;
                    }
                }
            }
        }
    }

    ll ans = 0;
    for (int i = 0; i < pop; i++)
    {
        ans += w[son_y[i]][i];
    }
    return ans;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值