HDU 2255 奔小康赚大钱 KM算法学习 附详细注释

推荐KM算法blog:https://blog.csdn.net/sixdaycoder/article/details/47720471
KM算法步骤

  1. 用邻接矩阵或者邻接表来存储图,注意,如果只是想求最大权值匹配而不是要求是完全匹配的话,需要把各个不相连的边权值设置为0.
  2. 运用贪心思想初始化标杆。即设定X,Y的初值。x部设置max(g[x][y]),y部设置0.
  3. 运用匈牙利算法找到完备匹配。
  4. 如果找不到,就通过修改标杆,增加一些边。
  5. 重复3,4的步骤,直到完全匹配时才可结束。
  • 时间复杂分析
    常规算法时间复杂度是 O ( n 4 ) O(n^4) O(n4),再加上 s l a c k slack slack松弛数组之后可达到 O ( n 3 ) O(n^3) O(n3)

  • 最大权匹配和最小权匹配说明
    此为求最大权匹配,若求最小权匹配,我们只需要将权值取相反数,结果取相反数即可。点的编号从1开始。

  • 算法实现

/**
  *@filename:KM算法
  *@author: pursuit
  *@created: 2021-08-06 11:30
**/
#include <bits/stdc++.h>
#define debug(a) cout << "debug : " << (#a)<< " = " << a << endl;

using namespace std;

typedef pair<int,int> pii;
typedef long long ll;
const int N = 300 + 10;
const int P = 1e9 + 7;
const int INF = 0x3f3f3f3f;

int nx,ny;//两边x和y的点数。
bool visX[N],visY[N];//判断是否已经加入增广路。
int g[N][N];//二部图。
int linker[N],lx[N],ly[N];//linker数组表示y中的各点匹配状态,lx和ly分别代表x和y的顶标
int slack[N];//松弛函数,slack[j]保存的是当前结点j相连的结点i的min{lx[i] + ly[j] - weight(x,y)}。
bool dfs(int x){
    //进入的都是x部的顶点。
    visX[x] = true;
    for(int y = 1; y <= ny; ++ y){
        if(visY[y])continue;//判断是否在交错路中。
        int temp = lx[x] + ly[y] - g[x][y];
        if(temp == 0){
            //说明x和y的边在相关子图中。
            visY[y] = true;
            if(linker[y] == -1 || dfs(linker[y])){
                //这里利用匈牙利算法思想,能让就让的原则。
                linker[y] = x;
                return true;
            }
        }
        else if(slack[y] > temp){
            slack[y] = temp;//(x,y)不在相等子图中且y不在交错树中。
        }
    }
    return false;
}
ll KM(){
    memset(linker,-1,sizeof(linker));//初始化为-1,y部所有顶点暂未匹配。
    memset(ly,0,sizeof(ly));//即第二步。
    for(int i = 1; i <= nx; ++ i){
        lx[i] = -INF;
        for(int j = 1; j <= ny; ++ j){
            lx[i] = max(lx[i],g[i][j]);//获取边权最大值。
        }
    }
    for(int x = 1; x <= nx; ++ x){
        for(int y = 1; y <= ny; ++ y){
            slack[y] = INF;//每次换新的结点都需要初始化slack。
        }
        while(true){
            memset(visX,false,sizeof(visX));
            memset(visY,false,sizeof(visY));
            if(dfs(x))break;//说明匹配成功。
            int d = INF;
            //匹配失败,x一定在交错路中,而y不在。
            for(int y = 1; y <= ny; ++ y){
                //获取最小值。
                if(!visY[y] && d > slack[y]){
                    d = slack[y];
                }
            }
            for(int i = 1; i <= nx; ++ i){
                if(visX[i]){
                    lx[i] -= d;
                }
            }
            for(int y = 1; y <= ny; ++ y){
                //修改顶标后同时也要修改slack数组,这是因为lx[i]减小了d。而slack[j] = min(lx[i] + ly[j] - g[i][j])。
                if(visY[y]){
                    ly[y] += d;
                }
                else{
                    slack[y] -= d;
                }
            }
        }
    }
    ll ans = 0;
    for(int y = 1; y <= ny; ++ y){
        if(linker[y] != -1){
            ans += g[linker[y]][y];
        }
    }
    return ans;
}
void solve(){
}
int main(){	
    int n;//房子数量。
    while(~scanf("%d", &n)){
        for(int i = 1; i <= n; ++ i){
            for(int j = 1; j <= n; ++ j){
                scanf("%d", &g[i][j]);
            }
        }
        nx = n,ny = n;
        printf("%lld\n", KM());
        solve();
    }
    return 0;
}
posted @ 2022-03-26 16:48  unique_pursuit  阅读(51)  评论(0)    收藏  举报