zoukankan      html  css  js  c++  java
  • 最大权完美匹配:KM算法的优化

    KM算法

    设二分图的两部分点集分别为 $X={X_1, X_2, ldots, X_n}$ 和 $Y={Y_1, Y_2, ldots, Y_m}$, $left<X_i, Y_j ight>$ 的边权为 $w_{ij}$.

    给两部分点集分别赋点权 ${A_i}, {B_i}$, 使得 $A_i+B_j ge w_{ij}$. 取等的边的生成子图叫做相等子图。那么相等子图的完美匹配就是最大权匹配。我们需要适当选取权值,使相等子图有完美匹配。

    算法流程如下:

    1. 令 $X=emptyset$, $B_j=0$.
    2. 对于 $k=1, 2, ldots, n$:
      1. 在 $X$ 中加入 $X_k$, 取 $A_k=max{w_{kj}}$.
      2. 搜索一条从 $X_k$ 到 $Y$ 中未匹配点的交错路。
      3. 如果交错路存在:
        1. 修改匹配。
        2. 令 $k gets k+1$ 重复 $(2)$.
      4. 如果交错路不存在,记搜索树(此时叫做交错树 $M$)顶点集与 $X$ 的交为 $X'$, 与 $Y$ 的交为 $Y'$.
        1. 取 $d=min{A_i+B_j-w_{ij} mid X_i in X', left<X_i, Y_j ight> otin M}$.
        2. 将 $X'$ 中的所有点权减小 $d$, $Y'$ 中的所有点权增大 $d$. 此时 $A_i+B_j ge w_{ij}$ 仍然满足,交错树上的边仍然属于相等子图,且至少有一条与交错树相邻的相等子图中的边。
        3. 重复 $(2.2)$.

    KM算法需要对每回修改后的子图重新搜索交错路,时间复杂度可达 $O(n^4)$.

    优化

    由于原交错树仍然是可用的,我们考虑不重新搜索交错路,而是在原交错树上直接扩展新边。

    具体地,我们在加入 $X_k$ 的过程中,由 $X_k$ 开始扩展交错树。

    对于每个 $Y'$ 中的点,记录它的父结点;对于 $Y_j in Y setminus Y'$, 我们维护 $slack_j=min{A_i+B_j-w_{ij} mid X_i in X'}$, 取得最小值的 $X_i$ 是它的准父结点(有多个任取一个)。

    扩展的流程是:

    1. 取出 $d=min{slack_j}$. 特别地,当 $d=0$ 是就是继续沿原相等子图扩展。
    2. 将 $X'$ 中的所有点权减小 $d$, $Y'$ 中的所有点权增大 $d$, 相应地,所有 $slack$ 减去 $d$.
    3. 将 $slack_j$ 取得最小值的 $Y_j$(有多个任取一个)加入 $Y'$, $Y_j$ 的父结点就是原准父结点。
    4. 若所加入的 $Y_j$ 还未匹配,说明已经找到交错路,顺着从 $X_k$ 到 $Y_j$ 的路径匹配。
    5. 若所加入的 $Y_j$ 已经匹配,将其匹配点加入 $X'$, 更新 $Y'$ 中各点的 $slack$ 和准父结点,重复扩展流程。

    在实现上,我们记 $match_j$ 表示 $Y_j$ 的匹配点,$pre_j$ 表示 $Y_j$ 的(准)父结点的匹配点,不存在记为 $0$.

    示例代码

    示例:假设 $n=m le 500$, 所有边权和答案绝对值小于 $10^{18}$. 输入 $n$ 和边权,输出 $Y_j$ 的匹配点。

     1 #include <bits/stdc++.h>
     2 const int N=501;
     3 int n, match[N];
     4 bool vis[N];
     5 long long f[N][N], a[N], b[N], slack[N], pre[N];
     6 template<class T1, class T2> bool cmin(T1 &a, const T2 &b)
     7 {
     8     return b<a?(a=b, true):false;
     9 }
    10 template<class T1, class T2> bool cmax(T1 &a, const T2 &b)
    11 {
    12     return a<b?(a=b, true):false;
    13 }
    14 int main()
    15 {
    16     scanf("%d", &n);
    17     for(int i=1; i<=n; ++i) {
    18         for(int j=1; j<=n; ++j)
    19             scanf("%d", f[i]+j);
    20         a[i]=*std::max_element(f[i]+1, f[i]+n+1);
    21     }
    22     for(int i=1; i<=n; ++i) {
    23         int x=0, cho;
    24         memset(vis+1, 0, n);
    25         memset(pre+1, 0, n*sizeof(int));
    26         memset(slack+1, 63, n*sizeof(long long));
    27         match[0]=i;
    28         do {
    29             int u=match[x];
    30             long long min=1e18;
    31             vis[x]=true;
    32             for(int v=1; v<=n; ++v) {
    33                 if(!vis[v]) {
    34                     long long t=a[u]+b[v]-f[u][v];
    35                     if(cmin(slack[v], t))
    36                         pre[v]=x;
    37                     if(cmin(min, slack[v]))
    38                         cho=v;
    39                 }
    40             }
    41             for(int j=0; j<=n; ++j) {
    42                 if(vis[j]) {
    43                     a[match[j]]-=min;
    44                     b[j]+=min;
    45                 } else
    46                     slack[j]-=min;
    47             }
    48             x=cho;
    49         } while(match[x]);
    50         while(x) {
    51             match[x]=match[pre[x]];
    52             x=pre[x];
    53         }
    54     }
    55     for(int i=1; i<=n; ++i)
    56         printf("%d%c", match[i], " 
    "[i==n]);
    57     return 0;
    58 }
  • 相关阅读:
    学习笔记:字符串-Hash
    模板:高精度
    关于我自己
    学习笔记:数学-GCD与LCM-素数筛法
    学习笔记:数学-GCD与LCM-唯一分解定理(质因数分解)
    学习笔记:数学-GCD与LCM-整除的基础概念
    题解 洛谷P1990 覆盖墙壁
    学习笔记:平衡树-splay
    npm发布myself的插件
    javascript API文档
  • 原文地址:https://www.cnblogs.com/nealchen/p/improved-KM.html
Copyright © 2011-2022 走看看