zoukankan      html  css  js  c++  java
  • 二分图最佳匹配

    (转)KM算法是通过给每个顶点一个标号(叫做顶标)来把求最大权匹配的问题转化为求完备匹配的问题的。设顶点Xi的顶标为A[i],顶点Yi的顶标为B [i],顶点Xi与Yj之间的边权为w[i,j]。在算法执行过程中的任一时刻,对于任一条边(i,j),A[i]+B[j]>=w[i,j]始终 成立。KM算法的正确性基于以下定理:
      若由二分图中所有满足A[i]+B[j]=w[i,j]的边(i,j)构成的子图(称做相等子图)有完备匹配,那么这个完备匹配就是二分图的最大权匹配。
      这个定理是显然的。因为对于二分图的任意一个匹配,如果它包含于相等子图,那么它的边权和等于所有顶点的顶标和;如果它有的边不包含于相等子图,那么它的边权和小于所有顶点的顶标和。所以相等子图的完备匹配一定是二分图的最大权匹配。
      初始时为了使A[i]+B[j]>=w[i,j]恒成立,令A[i]为所有与顶点Xi关联的边的最大权,B[j]=0。如果当前的相等子图没有完备匹配,就按下面的方法修改顶标以使扩大相等子图,直到相等子图具有完备匹配为止。
      我们求当前相等子图的完备匹配失败了,是因为对于某个X顶点,我们找不到一条从它出发的交错路。这时我们获得了一棵交错树,它的叶子结点全部是X顶点。现在我们把交错树中X顶点的顶标全都减小某个值d,Y顶点的顶标全都增加同一个值d,那么我们会发现:
    两端都在交错树中的边(i,j),A[i]+B[j]的值没有变化。也就是说,它原来属于相等子图,现在仍属于相等子图。
    两端都不在交错树中的边(i,j),A[i]和B[j]都没有变化。也就是说,它原来属于(或不属于)相等子图,现在仍属于(或不属于)相等子图。
    X端不在交错树中,Y端在交错树中的边(i,j),它的A[i]+B[j]的值有所增大。它原来不属于相等子图,现在仍不属于相等子图。
    X端在交错树中,Y端不在交错树中的边(i,j),它的A[i]+B[j]的值有所减小。也就说,它原来不属于相等子图,现在可能进入了相等子图,因而使相等子图得到了扩大。
      现在的问题就是求d值了。为了使A[i]+B[j]>=w[i,j]始终成立,且至少有一条边进入相等子图,d应该等于min{A[i]+B[j]-w[i,j]|Xi在交错树中,Yi不在交错树中}。
      以上就是KM算法的基本思路。但是朴素的实现方法,时间复杂度为O(n4)——需要找O(n)次增广路,每次增广最多需要修改O(n)次顶 标,每次修改顶标时由于要枚举边来求d值,复杂度为O(n2)。实际上KM算法的复杂度是可以做到O(n3)的。我们给每个Y顶点一个“松弛量”函数 slack,每次开始找增广路时初始化为无穷大。在寻找增广路的过程中,检查边(i,j)时,如果它不在相等子图中,则让slack[j]变成原值与A [i]+B[j]-w[i,j]的较小值。这样,在修改顶标时,取所有不在交错树中的Y顶点的slack值中的最小值作为d值即可。但还要注意一点:修改 顶标后,要把所有的slack值都减去d。

     

    POJ 2195

    http://acm.pku.edu.cn/JudgeOnline/problem?id=2195

    HDU 1533

    http://acm.hdu.edu.cn/showproblem.php?pid=1533

    第一道二分图最佳匹配题,简单题,会二分最佳的这题也就会。就建图的时候要把距离算出来!

    代码
    #include <iostream>
    #include
    <cmath>
    using namespace std;

    int n,m;
    int num1,num2;
    char map[150][150]; //初始输入
    int mat[150][150]; //记录距离
    int house[150][2]; //房子坐标
    int man[150][2]; //人的坐标
    int pl[150],pr[150]; //左右顶标
    int visl[150],visr[150]; //是否被访问
    int match[150]; //匹配
    int stack[150];

    int Min(int a,int b)
    {
    return a>b?b:a;
    }

    bool find(int a) // 找增广路径
    {
    int i;
    visl[a]
    = 1;
    for(i=0;i<num2;i++)
    {
    if(visr[i] == 0)
    {
    int val = pl[a] + pr[i] - mat[a][i];
    if(val == 0)
    {
    visr[i]
    = 1;
    if(match[i] == -1 || find(match[i]))
    {
    match[i]
    = a;
    return 1;
    }
    }
    else
    {
    if(val < stack[i])
    stack[i]
    = val;
    }
    }
    }

    return 0;
    }

    int km()
    {
    int i,j,k;
    //初始化顶标
    for(i=0;i<num1;i++)
    {
    pl[i]
    = -0x7ffffff;
    pr[i]
    = 0;
    for(j=0;j<num2;j++)
    {
    if(mat[i][j] > pl[i])
    pl[i]
    = mat[i][j];
    }
    }

    memset(match,
    -1,sizeof(match));
    for(k=0;k<num1;k++)
    {
    for(i=0;i<150;i++)
    stack[i]
    = 0x7fffffff;

    while(1)
    {
    memset(visl,
    0,sizeof(visl));
    memset(visr,
    0,sizeof(visr));
    if(find(k))
    break;

    int d = 0x7ffffff;
    for(j=0;j<num2;j++)
    {
    if(!visr[j])
    d
    = Min(stack[j] , d);
    }


    for(i=0;i<num1;i++)
    {
    if(visl[i])
    pl[i]
    -= d;
    if(visr[i])
    pr[i]
    += d;
    }
    }
    }

    int sum = 0;
    for(i=0;i<num1;i++)
    {
    sum
    += -1 * mat[match[i]][i];
    }

    return sum;
    }
    void Init()
    {
    int i,j;
    num1
    = 0; //房子个数
    num2 = 0; //人的个数
    for(i=0;i<n;i++)
    {
    cin
    >>map[i];
    for(j=0;j<m;j++)
    {
    if(map[i][j] == 'H')
    {
    house[num1][
    0] = i;
    house[num1
    ++][1] = j;
    }
    if(map[i][j] == 'm')
    {
    man[num2][
    0] = i;
    man[num2
    ++][1] = j;
    }
    }
    }
    for(i=0;i<num1;i++)
    {
    for(j=0;j<num2;j++)
    {
    mat[i][j]
    = abs(house[i][0] - man[j][0]) + abs(house[i][1] - man[j][1]);
    mat[i][j]
    = - mat[i][j]; //因为是取最小权值,所以取反
    }
    }
    }

    int main()
    {
    while(scanf("%d%d",&n,&m)!=EOF && (n!=0 || m!=0))
    {
    Init();
    printf(
    "%d\n",km());
    }
    return 0;
    }

     

    HDU 3488

    http://acm.hdu.edu.cn/showproblem.php?pid=3488

    题目出的比较隐藏,我记得当时我做的时候根本想不到是二分图最佳匹配,如果明白是二分图的话,还是很容易能理解的,就是一个最佳匹配!

    需要注意的一点是该题求的是最小值,先吧每条边取反,求最佳匹配,输出的时候再取反回来!

     

    代码
    #include <iostream>
    using namespace std;

    #define min(a,b) (a>b?b:a);

    const long maxn = 205;
    const long inf = 1000000000;
    int n,m; //n:点数 m:边数
    int nl,nr;
    int pl[maxn],pr[maxn]; //左右顶标
    int map[maxn][maxn];
    int match[maxn]; //匹配边
    int stack[maxn];
    bool visl[maxn],visr[maxn];

    void Init()
    {
    int i,j;
    int a,b,c;
    scanf(
    "%d%d",&n,&m);

    nl
    = nr = n;
    for(i=0;i<maxn;i++)
    for(j=0;j<maxn;j++)
    map[i][j]
    = inf;


    for(i=0;i<m;i++)
    {
    scanf(
    "%d%d%d",&a,&b,&c);
    if(c < map[a][b])
    {
    map[a][b]
    = c;
    }
    }

    for(i=1;i<=nl;i++)
    {
    for(j=1;j<=nr;j++)
    map[i][j]
    *= -1;
    }
    }

    bool find(int dir)
    {
    int i,j;
    visl[dir]
    = 1;

    for(i=1;i<=nr;i++)
    {
    if(0 == visr[i])
    {
    int val = pl[dir] + pr[i] - map[dir][i];

    if(0 == val)
    {
    visr[i]
    = 1;
    if(match[i] == -1 || find(match[i]))
    {
    match[i]
    = dir;
    return true;
    }
    }
    else
    {
    if(val < stack[i])
    stack[i]
    = val;
    }
    }
    }

    return false;
    }

    void KM()
    {
    int i,j,k;

    /*初始化顶标*/
    for(i=1;i<=nl;i++)
    {
    pl[i]
    = -inf;
    pr[i]
    = 0;
    for(j=1;j<=nr;j++)
    {
    if(map[i][j] > pl[i])
    pl[i]
    = map[i][j];
    }
    }

    memset(match,
    -1,sizeof(match));

    for(k=1;k<=nl;k++) //左边的点
    {
    fill(stack,stack
    +maxn,inf);

    while(1)
    {
    memset(visl,
    0,sizeof(visl));
    memset(visr,
    0,sizeof(visr));

    if(find(k))
    break;

    int d = inf;

    for(i=1;i<=nr;i++)
    {
    if(0 == visr[i])
    {
    d
    = min(stack[i],d);
    }
    }

    for(i=1;i<=nl;i++)
    if(1 == visl[i])
    pl[i]
    -= d;

    for(i=1;i<=nr;i++)
    if(1 == visr[i])
    pr[i]
    += d;
    }
    }
    }

    void Print()
    {
    int sum = 0;
    int i;
    for(i=1;i<=nr;i++)
    {
    if(match[i] != -1)
    sum
    += -1 * map[match[i]][i];
    }

    printf(
    "%d\n",sum);
    }

    int main()
    {
    int t;
    scanf(
    "%d",&t);
    while(t--)
    {
    Init();
    KM();
    Print();
    }
    return 0;
    }

     

    HDU 2448

    http://acm.hdu.edu.cn/showproblem.php?pid=2448

    最短路径+二分匹配

    先求出每条船所在的采矿点跟所有港口的最短距离,然后对这个图进行二分最佳匹配就行!

    注意一点:因为船进入港口之后就不会再出来,所以输入时采矿点的港口的距离是单向的

     

    代码
    #include <iostream>
    using namespace std;

    #define min(a,b) (a>b?b:a)
    typedef
    int elem_t;
    const long maxn = 310;
    const long inf = 1000000000;
    int n,m,p,q; //n个港口,m个采矿点
    int dir[maxn]; //船一开始停留的位置
    int map[maxn][maxn]; //初始图,1 - n 表示港口, n+1 - n+m 表示采矿点
    int Min[maxn][maxn]; //各个点的最短记录
    int pre[maxn][maxn]; //路径保存,这题没用

    int nl,nr;
    int pl[maxn],pr[maxn]; //左右顶标
    bool visl[maxn],visr[maxn]; //X和Y是否被访问
    int mat[maxn][maxn]; //建立起来的二分图
    int match[maxn]; //Y中与X的匹配关系
    int stack; //最小d值

    void Init()
    {
    //原图的输入
    int i,j;
    int a,b,c;
    for(i=1;i<=n;i++)
    {
    scanf(
    "%d",&dir[i]);
    dir[i]
    += n;
    }

    for(i=0;i<maxn;i++)
    for(j=0;j<maxn;j++)
    map[i][j]
    = inf;

    for(i=1;i<=p;i++)
    {
    scanf(
    "%d%d%d",&a,&b,&c);
    {
    a
    += n;
    b
    += n;
    if(c < map[a][b])
    map[a][b]
    = map[b][a] = c; //这里是双向的
    }
    }

    for(i=1;i<=q;i++)
    {
    scanf(
    "%d%d%d",&a,&b,&c);
    b
    += n;
    if(c < map[b][a])
    map[b][a]
    = c; //就是这里,应该是单向的
    }
    }

    //floyd求最短距离
    void floyd_warshall(int n,elem_t mat[][maxn],elem_t min[][maxn],int pre[][maxn]){
    int i,j,k;
    for (i=1;i<=n;i++)
    for (j=1;j<=n;j++)
    min[i][j]
    =mat[i][j],pre[i][j]=(i==j)?-1:i;
    for (k=1;k<=n;k++)
    for (i=1;i<=n;i++)
    for (j=1;j<=n;j++)
    if (min[i][k]+min[k][j]<min[i][j])
    min[i][j]
    =min[i][k]+min[k][j],pre[i][j]=pre[k][j];
    }

    void _Init()
    {
    //对二分图的建图
    int i,j;
    nl
    = nr = n;
    for(i=1;i<=n;i++)
    {
    for(j=1;j<=n;j++)
    {
    mat[i][j]
    = Min[dir[i]][j];
    }
    }

    for(i=1;i<=n;i++)
    {
    for(j=1;j<=n;j++)
    mat[i][j]
    *= -1;
    }
    }

    bool find(int a)
    {
    int i,j;
    visl[a]
    = 1;

    for(i=1;i<=nr;i++)
    {
    if(0 == visr[i])
    {
    int val = pl[a] + pr[i] - mat[a][i];

    if(0 == val)
    {
    visr[i]
    = 1;
    if(-1 == match[i] || find(match[i]))
    {
    match[i]
    = a;
    return true;
    }
    }
    else
    {
    if(val < stack)
    stack
    = val;
    }
    }
    }

    return false;
    }
    void KM()
    {
    int i,j;
    for(i=1;i<=nl;i++)
    {
    pl[i]
    = -1 * inf;;
    pr[i]
    = 0;
    for(j=1;j<=nr;j++)
    {
    if(mat[i][j] > pl[i])
    pl[i]
    = mat[i][j];
    }
    }

    memset(match,
    -1,sizeof(match));
    for(j=1;j<=nl;j++)
    {
    stack
    = inf;
    while(1)
    {
    memset(visl,
    0,sizeof(visl));
    memset(visr,
    0,sizeof(visr));

    if(find(j))
    break;

    int d = stack;

    for(i=1;i<=nl;i++)
    if(1 == visl[i])
    pl[i]
    -= d;

    for(i=1;i<=nr;i++)
    if(1 == visr[i])
    pr[i]
    += d;
    }
    }
    }

    void Print()
    {
    int i;
    int sum = 0;
    for(i=1;i<=nr;i++)
    {
    if(match[i] != -1)
    sum
    += -1 * mat[match[i]][i];
    }

    printf(
    "%d\n",sum);
    }

    int main()
    {
    while(scanf("%d%d%d%d",&n,&m,&p,&q)!=EOF)
    {
    Init();
    floyd_warshall(n
    +m,map,Min,pre);
    _Init();
    KM();
    Print();
    }
    return 0;
    }

    HDU 3722

    http://acm.hdu.edu.cn/showproblem.php?pid=3722

    简单二分最佳匹配

     

    HDU 2426

    http://acm.hdu.edu.cn/showproblem.php?pid=2426

    也是一道简答的二分匹配题,初始化时把为负值的不要输入就行!

    但这题数据量大了一点,我如果用Lost的那个模板,用stack而不用stack数组拿来优化的话就会超时!

    代码
    #include <iostream>
    using namespace std;

    #define min(a,b) (a>b?b:a)
    const long maxn = 505;
    const long inf = 1000000000;

    int cas = 0;
    int E;
    int nl,nr;
    int pl[maxn],pr[maxn]; //左右顶标
    bool visl[maxn],visr[maxn]; //X和Y是否被访问
    int mat[maxn][maxn]; //建立起来的二分图
    int match[maxn]; //Y中与X的匹配关系
    int stack[maxn]; //最小d值

    void Init()
    {
    int i,j;
    int a,b,c;

    for(i=0;i<maxn;i++)
    for(j=0;j<maxn;j++)
    mat[i][j]
    = -inf;

    for(i=0;i<E;i++)
    {
    scanf(
    "%d%d%d",&a,&b,&c);
    if(c >= 0)
    {
    a
    += 1;
    b
    += 1;
    mat[a][b]
    = c;
    }
    }
    }

    bool find(int a)
    {
    int i,j;
    visl[a]
    = 1;

    for(i=1;i<=nr;i++)
    {
    if(0 == visr[i])
    {
    int val = pl[a] + pr[i] - mat[a][i];

    if(0 == val)
    {
    visr[i]
    = 1;
    if(-1 == match[i] || find(match[i]))
    {
    match[i]
    = a;
    return true;
    }
    }
    else
    {
    if(val < stack[i])
    stack[i]
    = val;
    }
    }
    }

    return false;
    }

    void KM()
    {
    if(E == 0)
    return;
    int i,j;
    for(i=1;i<=nl;i++)
    {
    //pl[i] = -1 * inf;
    pl[i] = 0;
    for(j=1;j<=nr;j++)
    {
    if(mat[i][j] > pl[i])
    pl[i]
    = mat[i][j];
    }
    }
    for(i=1;i<=nr;i++)
    pr[i]
    = 0;

    memset(match,
    -1,sizeof(match));

    for(j=1;j<=nl;j++)
    {
    fill(stack,stack
    +maxn,inf);
    while(1)
    {
    memset(visl,
    0,sizeof(visl));
    memset(visr,
    0,sizeof(visr));

    if(find(j))
    break;

    int d = inf;
    for(i=1;i<=nr;i++)
    {
    if(visr[i] == 0)
    {
    d
    = min(stack[i],d);
    }
    }

    for(i=1;i<=nl;i++)
    if(1 == visl[i])
    pl[i]
    -= d;

    for(i=1;i<=nr;i++)
    {
    if(1 == visr[i])
    pr[i]
    += d;
    //else
    stack[i] -= d;
    }
    }
    }
    }

    void Print()
    {
    int i;
    int sum = 0;
    int sumt = 0;
    printf(
    "Case %d: ",++cas);

    for(i=1;i<=nr;i++)
    {
    if(match[i] != -1 && mat[match[i]][i] != -inf)
    {
    sumt
    ++;
    sum
    += mat[match[i]][i];
    }
    }
    if(sumt < nl)
    {
    sum
    = -1;
    }

    printf(
    "%d\n",sum);
    }

    int main()
    {
    while(scanf("%d%d%d",&nl,&nr,&E) != EOF)
    {
    Init();
    if(E == 0)
    {
    printf(
    "Case %d: ", ++cas);
    printf(
    "-1\n");
    continue;
    }

    KM();
    Print();
    }
    }

  • 相关阅读:
    python之路-笔录3[BOM&DOM&JQuery]
    python之路-笔录2[CSS&JS]
    python 文件读写方式
    python -- 数据可视化(二)
    Django权限机制的实现
    视频云存储使用介绍
    linux安装phantomjs
    基于redis实现分布式锁
    基于数据库实现分布式锁
    分布式锁
  • 原文地址:https://www.cnblogs.com/silencExplode/p/1903509.html
Copyright © 2011-2022 走看看