• 【二分图】匈牙利 & KM


    【二分图】匈牙利 & KM

    二分图

    概念:

    一个图 \(G=(V,E)\) 是无向图,如果顶点 \(V\) 可以分成两个互不相交地子集 \(X,Y\)

    且任意一条边的两个顶点一个在 \(X\) 中,一个在 \(Y\) 中,则称 \(G\) 是二分图


    性质:

    当且仅当无向图 \(G\) 的所有环都是偶环时, \(G\) 才是个二分图


    判定:

    可从任意一点开始 \(\text{dfs}\),按距离编号

    如果要编号的点已经被编号,可根据奇偶判断是否是二分图

    bool dfs(int u) {
        for (int i = lst[u], v; i; i = nxt[i]) {
            v = to[i];
            if (cl[u] == cl[v])
                return 0;
            if (!cl[v]) {
                cl[v] = 3 - cl[u];
                if (!dfs(v))
                    return 0;
            }
        }
        return 1;
    }
    int main() {
        cl[1] = 1;
        dfs(1);
    }
    

    二分图的匹配

    概念:

    一个二分图 \(G\) 的一个子图 \(M\), 若在 \(M\) 中的任意两条边都不依附同一个顶点

    则称 \(M\) 是一个匹配


    最大匹配:

    即选择边数最大的匹配

    完备匹配:

    某一部的顶点全部与匹配中的某条边关联

    完美匹配:

    所有顶点都和匹配中的某条边关联

    增广路

    定义:

    \(M\) 是二分图 \(G\) 的已匹配边的集合,若 \(P\) 是一条联通两个在不同部的未匹配点的路径,

    且路径上匹配边与未匹配边交替出现,则 \(P\) 是相对于 \(M\) 的增广路。


    性质:

    • 第一条和最后一条都是未匹配边,边数为奇数

      因为要联通两个在不同部的未匹配点

    • 一个增广路径 \(P\) 经过取反可以得到一个更大的匹配 \(M'\)

    • \(M\)\(G\) 的最大匹配当且仅当不存在相对于 \(M\) 的增广路

    最大匹配

    匈牙利算法

    根据增广路的性质,我们也可以想到一种算法

    1. 清空 \(M\)
    2. 寻找 \(M\) 的增广路 \(P\),通过取反得到更大的 \(M'\) 代替 \(M\)
    3. 重复 (2) 直到找不到增广路为止

    这就是匈牙利算法

    实现

    \(\text{dfs}\) ,从 \(X\) 部的一个未匹配点开始,访问邻接点 \(v\) (一定是 \(Y\) 部的)

    • 如果 \(v\) 未匹配,则已找到一条增广路

    • 否则,找出 \(v\) 的匹配顶点 \(w\) (一定是 \(X\) 部的),则 \((w,v)\) 是匹配边

      因为要"取反",所以要使 \((w,v)\) 未匹配, \((u,v)\) 匹配。

      能实现这一点就需要从 \(w\) 出发找一条新增广路,如果行,则可以找到增广路

    实现:

    // cx[i] 是 X 部的点 i 匹配的 Y 部点
    // cy[i] 是 Y 部的点 i 匹配的 X 部点
    bool dfs(int u) {
    	for (int i = 1; i <= m; i++)
    		if (mp[u][i] && !vis[i]) {
    			vis[i] = 1;
    			if (!cy[i] || dfs(cy[i])) {
    				cx[u] = i, cy[i] = u;
    				return 1;
    			}
    		}
    	return 0;
    }
    inline void match() {
    	memset(cx, 0, sizeof(cx));
    	memset(cy, 0, sizeof(cy));
    	for (int i = 1; i <= n; i++) {
    		memset(vis, 0, sizeof(vis));
    		ans += dfs(i);
    	}
    }
    

    时间复杂度:

    • 邻接矩阵:\(O(n^3)\)
    • 前向星:\(O(nm)\)

    最大匹配的性质

    1. 最小点覆盖 = 最大匹配

      最小点覆盖:选择最少的点使得每条边都至少和其中一个点关联

      • 证明:最大匹配能保证剩下的边都与至少一个点关联
    2. 最小边覆盖 = 总点数 - 最大匹配

      最小边覆盖:选择最少的边去覆盖所有点

      • 证明:设总点数是 \(n\),最大匹配是 \(m\)

        则最大匹配能覆盖 \(2m\) 个点,设剩下 \(a\) 个点

        则这 \(a\) 个点需要单独用 \(a\) 条边覆盖,最小边覆盖 = \(m+a\)

        因为 \(2m+a=n\)

        所以最小边覆盖 = \((2m+a)-m=n-m\)

    3. 最大点独立集 = 总点数 - 最小点覆盖

      最大点独立集:在二分图中选出最多的顶点,使得任两点之间没有边相连

      • 证明:删去最小点覆盖的点集,对应的边也没有了,剩下的点就是独立集

        因为是最小点覆盖,减去后就是最大点独立集

    最佳匹配

    如果边有权,则权和最大的匹配叫最佳匹配

    有连接 \(X,Y\) 部的顶点 \(X_iY_j\) 的一条边 \(w_{i,j}\) ,要求一种使 \(\sum w_{i,j}\) 最大的匹配


    顶标:给顶点的一个标号

    \(X_i\) 的顶标 \(A_i\)\(Y_j\) 的顶标 \(B_j\)则在任意时刻需要满足 \(A_i+B_j\ge w_{i,j}\) 成立


    相等子图:由 \(A_i+B_j=w_{i,j}\) 的边构成的字图

    性质:如果相等字图有完备匹配,那么这个完备匹配是最佳匹配

    KM

    核心:通过修改顶标使得能找到完备匹配

    1. 初始化: \(A_i\) 为所有与 \(X_i\) 相连边的最大权, \(B_i=0\)

    2. 寻找完备匹配失败,得到一条路径,叫做交错树

      将交错树中 \(X\) 部的顶标全部减去 \(d\)\(Y\) 部的顶标全部加上 \(d\) ,会发现

      • 两端都在交错树中的边, \(A_i+B_j\) 不变,仍在相等字图中

        都不在,仍然不在相等字图

      • \(X\) 不在 \(Y\) 在,\(A_i+B_j\) 增大,仍然不在相等字图

      • \(X\)\(Y\) 不在,\(A_i+B_j\) 减小,现在可能在相等字图中,使得相等字图扩大

      为了使至少有一条边进入相等字图,且 \(A_i+B_j\ge w_{i,j}\) 恒成立

      \(d\) 应该等于 \(\min A_i+B_j-w_{i,j}\)

    3. 重复 (2) 直到找到完备匹配

    复杂度:朴素实现是 \(O(n^4)\)

    在相等字图上找增广路 \(O(n^2)\) ,每次改顶标最多 \(n\) 次增广路,要改 \(n\) 次顶标

    // lx, ly :顶标
    // cy[i] 是 y 部点 i 匹配的 X 部点
    bool dfs(int x) {
       vx[x] = 1;
       for (int i = 1, cz; i <= n; i++) {
           if (vy[i]) continue;
           cz = lx[x] + ly[i] - mp[x][i];
           if (cz == 0) {
               vy[i] = 1;
               if (!cy[i] || dfs(cy[i])) {
                   cy[i] = x;
                   return 1;
               }
           } else lack = min(lack, cz);
       }
       return 0;
    }
    inline void KM() {
       memset(cy, 0, sizeof(cy));
       for (int i = 1; i <= n; i++)
           for (;;) {
               memset(vx, 0, sizeof(vx));
               memset(vy, 0, sizeof(vy));
               lack = 2e9;
               if (dfs(i)) break;
               for (int j = 1; j <= n; j++) {
                   if (vx[j]) lx[j] -= lack;
                   if (vy[j]) ly[j] += lack;
               }
           }
    }
    

    \(O(n^3)\) 的做法

    其实是可以实现 \(O(n^3)\) 的,

    • 我们给每个 \(Y\) 顶点一个松弛量 \(\text{slack}\) ,初始为正无穷

    • 对于每条边 \((i, j)\), 若不在相等字图中,\(\text{slack}_j=\min(A_i+B_j-w_{i,j})\)

    • 修改顶标时 \(d\) 取所有不在交错树中的 \(Y\) 部点\(\text{slack}\) 的最小值

    • 修改完后,所有不在交错树中的 \(Y\) 部点\(\text{slack}\) 都减去 \(d\)

    int slk[N], pre[N], mat[N];
    // mat 等同于 cy
    inline void bfs(int st) {
        memset(pre, 0, sizeof(pre));
        for (int i = 1; i <= n; i++) slk[i] = 1e9;
        int x, y = 0, del, pos;
        mat[y] = st;
        do {
            x = mat[y], vy[y] = 1, del = 1e9;
            for (int i = 1; i <= n; i++) {
                if (vy[i]) continue;
                if (slk[i] > lx[x] + ly[i] - mp[x][i])
                    slk[i] = lx[x] + ly[i] - mp[x][i], pre[i] = y;
                if (slk[i] < del) del = slk[i], pos = i;
            }
            for (int i = 0 ; i <= n; i++) {
                if (vy[i]) lx[mat[i]] -= del, ly[i] += del;
                else slk[i] -= del;
            }
            y = pos;
        } while (mat[y]);
        while (y) mat[y] = mat[pre[y]], y = pre[y];
    }
    inline void KM() {
        memset(mat, 0, sizeof(mat));
        memset(lx, 0, sizeof(lx));
        memset(ly, 0, sizeof(ly));
        for (int i = 1; i <= n; i++) {
            memset(vy, 0, sizeof(vy));
            bfs(i);
        }
    }
    

    答案

    for (int i = 1; i <= n; i++) {
        if (mp[mat[i]][i]==-1e9 || !mat[i]) {
            puts("-1");
            break;
        }
        ans += mp[mat[i]][i];
    }
    

    End

  • 相关阅读:
    对健康的一些思考
    对提问的一些思考
    UVA 10118 Free Candies(免费糖果)(dp记忆化搜索)
    UVA 10285 Longest Run on a Snowboard(最长的滑雪路径)(dp记忆化搜索)
    UVA 12186 Another Crisis(工人的请愿书)(树形dp)
    UVA 10003 Cutting Sticks(切木棍)(dp)
    UVA 11584 Partitioning by Palindromes(划分成回文串)(dp)
    【洛谷P1144】最短路计数【最短路】
    【洛谷P1144】最短路计数【最短路】
    【洛谷P1144】最短路计数【最短路】
  • 原文地址:https://www.cnblogs.com/KonjakLAF/p/15551711.html
Copyright © 2020-2023  润新知