KM最大权匹配入门训练

km算法主要还是通过和匈牙利算法一样的dfs不断求增广路。
大致过程可分为初始化可行性顶标、用匈牙利算法求完全匹配、求解失败则调整顶标值继续找、找到完全匹配后求和。

设顶点Xi的顶标为lx[i],顶点Yi的顶标为ly[i],顶点Xi与Yj之间的边权为w[i,j]。在算法执行过程中的任一时刻,对于任一条边(i,j),lx[i]+ly[j]>=w[i,j]始终成立。
KM算法的正确性基于以下定理:
若由二分图中所有满足lx[i]+ly[j]=w[i,j]的边(i,j)构成的子图(称做相等子图)有完备匹配,那么这个完备匹配就是二分图的最大权匹配。
这个定理是显然的。因为对于二分图的任意一个匹配,如果它包含于相等子图,那么它的边权和等于所有顶点的顶标和;如果它有的边不包含于相等子图,那么它的边权和小于所有顶点的顶标和。所以相等子图的完备匹配一定是二分图的最大权匹配。
初始时为了使lx[i]+ly[j]>=w[i,j]恒成立,令lx[i]为所有与顶点Xi关联的边的最大权,ly[j]=0。这个初始点标显然是可行的,并且,与任意一个X方点关联的边中至少有一条可行边;
然后,从每个X方点开始DFS增广。DFS增广的过程与最大匹配的Hungary算法基本相同,只是要注意两点:一是只找可行边,二是要把搜索过程中遍历到的X方点全部记下来(可以用vst搞一下),以进行后面的修改;
增广的结果有两种:若成功(找到了增广轨),则该点增广完成,进入下一个点的增广。若失败(没有找到增广轨),则需要改变一些点的标号,使得图中可行边的数量增加。方法为:将所有在增广轨中(就是在增广过程中遍历到)的X方点的标号全部减去一个常数d,所有在增广轨中的Y方点的标号全部加上一个常数d,则对于图中的任意一条边(i, j, W)(i为X方点,j为Y方点):
<1>i和j都在增广轨中:此时边(i, j)的(lx[i]+ly[j])值不变,也就是这条边的可行性不变(原来是可行边则现在仍是,原来不是则现在仍不是);
<2>i在增广轨中而j不在:此时边(i, j)的(lx[i]+ly[j])的值减少了d,也就是原来这条边不是可行边(否则j就会被遍历到了),而现在可能是;
<3>j在增广轨中而i不在:此时边(i, j)的(lx[i]+ly[j])的值增加了d,也就是原来这条边不是可行边(若这条边是可行边,则在遍历到j时会紧接着执行DFS(i),此时i就会被遍历到),现在仍不是;
<4>i和j都不在增广轨中:此时边(i, j)的(lx[i]+ly[j])值不变,也就是这条边的可行性不变。
这样,在进行了这一步修改操作后,图中原来的可行边仍可行,而原来不可行的边现在则可能变为可行边。那么d的值应取多少?显然,整个点标不能失去可行性,也就是对于上述的第<2>类边,其lx[i]+ly[j]>=W这一性质不能被改变,故取所有第<2>类边的(lx[i]+ly[j]-W)的最小值作为d值即可。这样一方面可以保证点标的可行性,另一方面,经过这一步后,图中至少会增加一条可行边。
修改后,继续对这个X方点DFS增广,若还失败则继续修改,直到成功为止;
下面分析整个算法的时间复杂度:每次修改后,图中至少会增加一条可行边,故最多增广M次、修改M次就可以找到仅由可行边组成的完全匹配(除非图中不存在完全匹配,这个可以通过预处理得到),故整个算法的时间复杂度为O(M * (N + 一次修改点标的时间))。而一次修改点标的时间取决于计算d值的时间,如果暴力枚举计算,这一步的时间为O(M),优化:可以对每个Y方点设立一个slk值,表示在DFS增广过程中,所有搜到的与该Y方点关联的边的(lx+ly-W)的最小值(这样的边的X方点必然在增广轨中)。每次DFS增广前,将所有Y方点的slk值设为+∞,若增广失败,则取所有不在增广轨中的Y方点的slk值的最小值为d值。这样一次修改点标的时间降为O(N),总时间复杂度降为O(NM)。

需要注意的一点是,在增广过程中需要记下每个X、Y方点是否被遍历到,即lx[i]、ly[j]。因此,在每次增广前(不是对每个X方点增广前)就要将所有lx和ly值清空。

hdu 2255
奔小康赚大钱

//完完全全的模板题
#include <cstdio>
#include <cstring>
#include <cctype>
#include <cmath>
#include <set>
#include <map>
#include <list>
#include <queue>
#include <deque>
#include <stack>
#include <string>
#include <bitset>
#include <vector>
#include <iostream>
#include <algorithm>
#define max(a,b) ((a)>(b)?(a):(b))
#define mem(a,b) memset(a,b,sizeof(a))
#define For(a,l,r) for(int a=l;a<r;a++)
using namespace std;

typedef  long long LL;
const LL  mod = 9973;
const LL  inf=0x3f3f3f3f;
const double pi=acos(-1);
const int N=320;//最大点数
const int M=1000020;// 最大边数

int nx,ny;
int g[N][N];
int linker[N],lx[N],ly[N];
int slack[N];
bool visx[N],visy[N];
bool dfs(int x)//寻找增广路
{
    visx[x]=true;
    for(int y=0;y<ny;y++)
    {
        if(visy[y])continue;
        int tep=lx[x]+ly[y]-g[x][y];
        if(tep==0)
        {
            visy[y]=true;
            if(linker[y]==-1||dfs(linker[y]))
            {
                linker[y]=x;
                return true;
            }
        }
        else if(slack[y]>tep)
            slack[y]=tep;
    }
    return false;
}
int KM()
{
    memset(linker,-1,sizeof(linker));
    memset(ly,0,sizeof(ly));
    for(int i=0;i<nx;i++)
    {
        lx[i]=-inf;
        for(int j=0;j<ny;j++)
            if(g[i][j]>lx[i])
                lx[i]=g[i][j];
    }
    for(int x=0;x<nx;x++)
    {
        for(int i=0;i<ny;i++)
            slack[i]=inf;
        while(true)
        {
            memset(visx,false,sizeof(visx));
            memset(visy,false,sizeof(visy));
            if(dfs(x))break;
            int d=inf;//不断修改顶标直到找到增广路
            for(int i=0;i<ny;i++)
                if(!visy[i]&&d>slack[i])
                    d=slack[i];
                for(int i=0;i<nx;i++)
                    if(visx[i])
                        lx[i]-=d;
                    for(int i=0;i<ny;i++)
                    {
                        if(visy[i])ly[i]+=d;
                        else slack[i]-=d;

                    }
        }
    }
    int res=0;
    for(int i=0;i<ny;i++)
        if(linker[i]!=-1)
            res+=g[linker[i]][i];
        return res;
}
int main()
{
    int n;
    while(scanf("%d",&n)==1)
    {
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++)
                scanf("%d",&g[i][j]);
            nx=ny=n;
            printf("%d\n",KM());
    }
    return 0;
}    

hdu 1533
Going Home
求最小权匹配 只需要把边权值取负,输出-KM()即可。

#include <cstdio>
#include <cstring>
#include <cctype>
#include <cmath>
#include <set>
#include <map>
#include <list>
#include <queue>
#include <deque>
#include <stack>
#include <string>
#include <bitset>
#include <vector>
#include <iostream>
#include <algorithm>
#define max(a,b) ((a)>(b)?(a):(b))
#define mem(a,b) memset(a,b,sizeof(a))
#define F first
#define S second
using namespace std;

typedef  long long LL;
const LL  mod = 9973;
const LL  inf=0x3f3f3f3f;
const double pi=acos(-1);
const int N=102;//最大点数
const int M=1000020;// 最大边数

char mapp[N][N]; 
pair<int,int>px[100],py[100];
int nx,ny;
int g[N][N];
int linker[N],lx[N],ly[N];
int slack[N];
bool visx[N],visy[N];
bool dfs(int x)
{
    visx[x]=true;
    for(int y=0;y<ny;y++)
    {
        if(visy[y])continue;
        int tep=lx[x]+ly[y]-g[x][y];
        if(tep==0)
        {
            visy[y]=true;
            if(linker[y]==-1||dfs(linker[y]))
            {
                linker[y]=x;
                return true;
            }
        }
        else if(slack[y]>tep)
            slack[y]=tep;
    }
    return false;
}
int KM()
{
    memset(linker,-1,sizeof(linker));
    memset(ly,0,sizeof(ly));
    for(int i=0;i<nx;i++)
    {
        lx[i]=-inf;
        for(int j=0;j<ny;j++)
            if(g[i][j]>lx[i])
                lx[i]=g[i][j];
    }
    for(int x=0;x<nx;x++)
    {
        for(int i=0;i<ny;i++)
            slack[i]=inf;
        while(true)
        {
            memset(visx,false,sizeof(visx));
            memset(visy,false,sizeof(visy));
            if(dfs(x))break;
            int d=inf;
            for(int i=0;i<ny;i++)
                if(!visy[i]&&d>slack[i])
                    d=slack[i];
                for(int i=0;i<nx;i++)
                    if(visx[i])
                        lx[i]-=d;
                    for(int i=0;i<ny;i++)
                    {
                        if(visy[i])ly[i]+=d;
                        else slack[i]-=d;
                    }
        }
    }
    int res=0;
    for(int i=0;i<ny;i++)
        if(linker[i]!=-1)
            res+=g[linker[i]][i];
        return res;
}
int main()
{
#ifdef LOCALHEI
    freopen("date.in","r",stdin);
    freopen("date.out","w",stdout);
#endif
    int n,m;
    while(scanf("%d%d",&n,&m)==2)
    {
        if(n==0&&m==0)
            break;
        int tx=0,ty=0;
        for(int i=0;i<n;i++)
            scanf("%s",mapp[i]);
        for(int i=0;i<n;i++)
            for(int j=0;j<m;j++)
            {
                if(mapp[i][j]=='H')
                    px[tx++]=make_pair(i,j);
                else if(mapp[i][j]=='m')
                    py[ty++]=make_pair(i,j);
            }
        for(int i=0;i<tx;i++)
            for(int j=0;j<ty;j++)
                g[i][j]=-(abs(px[i].F-py[j].F)+abs(px[i].S-py[j].S));
            nx=tx;
            ny=ty;
            printf("%d\n",-KM());
    }
    return 0;
}    

hdu1853
Cyclic Tour
找若干个环使边权值之和最小,这个为什么能用二分最大权匹配呢?
可以想象一下若要把所有点都走一遍肯定有n个入度n个出度,而二分匹配刚好是使一个出度指向另一个没有入度的点,这恰好是最大匹配的思想。主意有重边。

#include <cstdio>
#include <cstring>
#include <cctype>
#include <cmath>
#include <set>
#include <map>
#include <list>
#include <queue>
#include <deque>
#include <stack>
#include <string>
#include <bitset>
#include <vector>
#include <iostream>
#include <algorithm>
#define max(a,b) ((a)>(b)?(a):(b))
#define mem(a,b) memset(a,b,sizeof(a))
#define F first
#define S second
using namespace std;

typedef  long long LL;
const LL  mod = 9973;
const LL  inf=0x3f3f3f3f;
const double pi=acos(-1);
const int N=102;//最大点数
const int M=1000020;// 最大边数

int nx,ny;
int g[N][N];
int linker[N],lx[N],ly[N];
int slack[N];
bool visx[N],visy[N];
bool dfs(int x)
{
    visx[x]=true;
    for(int y=0;y<ny;y++)
    {
        if(visy[y])continue;
        int tep=lx[x]+ly[y]-g[x][y];
        if(tep==0)
        {
            visy[y]=true;
            if(linker[y]==-1||dfs(linker[y]))
            {
                linker[y]=x;
                return true;
            }
        }
        else if(slack[y]>tep)
            slack[y]=tep;
    }
    return false;
}
int KM()
{
    memset(linker,-1,sizeof(linker));
    memset(ly,0,sizeof(ly));
    for(int i=0;i<nx;i++)
    {
        lx[i]=-inf;
        for(int j=0;j<ny;j++)
            if(g[i][j]>lx[i])
                lx[i]=g[i][j];
    }
    for(int x=0;x<nx;x++)
    {
        for(int i=0;i<ny;i++)
            slack[i]=inf;
        while(true)
        {
            memset(visx,false,sizeof(visx));
            memset(visy,false,sizeof(visy));
            if(dfs(x))break;
            int d=inf;
            for(int i=0;i<ny;i++)
                if(!visy[i]&&d>slack[i])
                    d=slack[i];
                for(int i=0;i<nx;i++)
                    if(visx[i])
                        lx[i]-=d;
                    for(int i=0;i<ny;i++)
                    {
                        if(visy[i])ly[i]+=d;
                        else slack[i]-=d;
                    }
        }
    }
    int res=0;
    for(int i=0;i<ny;i++)
    {
        if(linker[i]==-1||g[linker[i]][i]==-inf)
            return 1;
        if(linker[i]!=-1)
            res+=g[linker[i]][i];
    }

        return res;
}
int main()
{
#ifdef LOCALHEI
    freopen("date.in","r",stdin);
    freopen("date.out","w",stdout);
#endif
    int n,m;
    while(scanf("%d%d",&n,&m)==2)
    {
        for(int i=0;i<n;i++)
            for(int j=0;j<n;j++)
                g[i][j]=-inf;
            int u,v,c;
            while(m--)
            {
                scanf("%d%d%d",&u,&v,&c);
                u--;
                v--;
                if(-c>g[u][v])
                g[u][v]=-c;
            }
            nx=n;
            ny=n;
            printf("%d\n",-KM());
    }
    return 0;
}   

你可能感兴趣的:(DFS,km,匈牙利,最大权)