KM最大权匹配入门训练

来源:互联网 发布:http传输数据 编辑:程序博客网 时间:2024/06/05 17:56

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 secondusing 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 secondusing 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;}   
0 0
原创粉丝点击