【原创】KM算法 ※HDU 2225 POJ 2195

来源:互联网 发布:java 接口 编辑:程序博客网 时间:2024/05/21 06:25

KM算法

KM是适用于二分图的算法,如果不清楚,可以查看这篇博客。

完美匹配与完备匹配与最佳匹配

完美匹配

一个二分图中,如果X部和Y部的顶点数相同,存在一个匹配,包含所有顶点,则称这个匹配为完美匹配
如图:
完美匹配
粉色匹配就是一个完美匹配。

完备匹配

一个二分图中,如果存在一个匹配,包含一个部的所有顶点,则称这个匹配为完备匹配
如图x部完备就是一个x部完备匹配。
y部完备像这样y部完备的匹配,也叫完备匹配。

最佳匹配

带权二分图权值最大完备匹配称为最佳匹配
注意:最佳匹配是一个完备匹配,而不一定是边权之和最大的匹配
举个栗子:
例子1
如图所示,最大权匹配是A——B’,权值为100。
最佳匹配为A——A‘,B——B’,权值为2。

寻找最佳匹配—KM算法

那么怎么找最佳匹配呢?

  1. 首先选择顶点数较少的为X部,初始时给每一个顶点设置顶标,X部顶点的顶标的值为该点关联的最大边的权值Y部的顶点顶标为0。
  2. 对于X部中的每个顶点,在相等子图中利用匈牙利算法找一条增广路径,如果没有找到,则修改顶标,扩大相等子图,继续找增广路径。
  3. 当X部的所有顶点都找到了增广路径后,则找到了完备匹配,此完备匹配即为最佳匹配。

步骤2提到了“相等子图”,什么是相等子图呢?
因为我们给每个顶点设置了顶标,我们称顶点顶标和等于该边边权的边为相等边,相等边组成的子图就是相等子图。
如图:
相等边
899=233+666,因此边AB是相等边。
那么相等子图就是:
相等子图

接着,我们怎么修改顶标?
这个地方打上重点!

如果从X部中的某个点Xi出发在相等子图中没有找到增广路径,我们是如何修改顶标的呢?如果我们没有找到增广路径,则我们一定找到了许多条从Xi出发并结束于X部的匹配边与未匹配边交替出现的路径,姑且称之为交错树。我们将交错树中X部的顶点顶标减去一个值d,交错树中属于Y部的顶点顶标加上一个值d。这个值后面要讲它如何计算。那么我们会发现:

两端都不在交错树中的边(i,j),其顶标也没有变化。也就是说,它原来属于(或不属于)相等子图,现在仍属于(或不属于)相等子图。

X端不在交错树中,Y端在交错树中的边(i,j),它的顶标和会增大。它原来不属于相等子图,现在仍不属于相等子图。

X端在交错树中,Y端不在交错树中的边(i,j),它的顶标和会减小。也就说,它原来不属于相等子图,现在可能进入了相等子图,因而使相等子图得到了扩大。

我们修改顶标的目的就是要扩大相等子图。为了保证至少有一条边进入相等子图,我们可以在交错树的边中寻找顶标和与边权之差最小的边,这就是前面说的d值。将交错树中属于X部的顶点减去d,交错树中属于Y部的顶点加上d。则可以保证至少有一条边扩充进入相等子图。

相等子图的性质

  1. 在任意时刻,相等子图上的最大权匹配一定小于等于相等子图的顶标和。
  2. 在任意时刻,相等子图的顶标和即为所有顶点的顶标和。
  3. 扩充相等子图后,相等子图的顶标和将会减小。
  4. 当相等子图的最大匹配为原图的完备匹配时,匹配边的权值和等于所有顶点的顶标和,此匹配即为最佳匹配。

KM算法的代码实现:

bool dfs(int s) //匈牙利算法找增广路径{    visx[s]=1;    for(int i=1;i<=cnty;i++)         if(!visy[i])        {            int t=wx[s]+wy[i]-dis[s][i];            if(t==0) //如果它是相等边            {                visy[i]=1;                if(linky[i]==0||dfs(linky[i]))                {                    linkx[s]=i,linky[i]=s;                    return true;                }            }            else if(t>0)  //找出边权与顶标和的最小的差值                if(t<minz)minz=t;        }    return false;}void km(){    memset(linkx,0,sizeof linkx); //linkx[i]表示与X部中点i匹配的点    memset(linky,0,sizeof linky);    for(int i=1;i<=cntx;i++)    {        while(1)        {            minz=INF;            memset(visx,0,sizeof visx);            memset(visy,0,sizeof visy);            if(dfs(i))break;            for(int j=1;j<=cntx;j++)  //将交错树中X部的点的顶标减去minz            if(visx[j])wx[j]-=minz;            for(int j=1;j<=cnty;j++) //将交错树中Y部的点的顶标加上minz            if(visy[j])wy[j]+=minz;        }    }}

例题

HDU2255 奔小康赚大钱

奔小康赚大钱 HDU - 2255
传说在遥远的地方有一个非常富裕的村落,有一天,村长决定进行制度改革:重新分配房子。
这可是一件大事,关系到人民的住房问题啊。村里共有n间房间,刚好有n家老百姓,考虑到每家都要有房住(如果有老百姓没房子住的话,容易引起不安定因素),每家必须分配到一间房子且只能得到一间房子。
另一方面,村长和另外的村领导希望得到最大的效益,这样村里的机构才会有钱.由于老百姓都比较富裕,他们都能对每一间房子在他们的经济范围内出一定的价格,比如有3间房子,一家老百姓可以对第一间出10万,对第2间出2万,对第3间出20万.(当然是在他们的经济范围内).现在这个问题就是村领导怎样分配房子才能使收入最大.(村民即使有钱购买一间房子但不一定能买到,要看村领导分配的).
Input
输入数据包含多组测试用例,每组数据的第一行输入n,表示房子的数量(也是老百姓家的数量),接下来有n行,每行n个数表示第i个村名对第j间房出的价格(n<=300)。
Output
请对每组数据输出最大的收入值,每组的输出占一行。

Sample Input
2
100 10
15 23
Sample Output
123

分析:

此,题,就,是,一,道,裸,K,M。

代码:

#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>using namespace std;#define clear(a) memset(a,0,sizeof a)const int N=301;const int INF=0x7fffffff;int n,delta;int visx[N],visy[N],linkx[N],linky[N],wx[N],wy[N];int dis[N][N];bool dfs(int x){    visx[x]=1;    for(int i=1;i<=n;i++)        if(!visy[i])        {            int k=wx[x]+wy[i]-dis[x][i];            if(!k)            {                visy[i]=1;                if(!linky[i]||dfs(linky[i]))                {                    linkx[x]=i;linky[i]=x;                    return 1;                }            }            else if(k<delta)                delta=k;        }    return 0;}int KM(){    for(int i=1;i<=n;i++)        while(1)        {            delta=INF;            clear(visx);clear(visy);            if(dfs(i))break;            for(int j=1;j<=n;j++)            {                if(visx[j])wx[j]-=delta;                if(visy[j])wy[j]+=delta;            }        }    int ans=0;    for(int i=1;i<=n;i++)    {        if(linky[i])ans+=wy[i];        if(linkx[i])ans+=wx[i];    }    return ans;}int main(){    while(~scanf("%d",&n))    {        for(int i=1;i<=n;i++)        {            scanf("%d",&dis[i][j]);            wx[i]=max(wx[i],dis[i][j]);        }        printf("%d\n",KM());        clear(dis);clear(wx);clear(wy);clear(linkx);clear(linky);    }}

POJ 2195 going home 回家

Going Home HDU - 1533

On a grid map there are n little men and n houses. In each unit time, every little man can move one unit step, either horizontally, or vertically, to an adjacent point. For each little man, you need to pay a $1 travel fee for every step he moves, until he enters a house. The task is complicated with the restriction that each house can accommodate only one little man.

Your task is to compute the minimum amount of money you need to pay in order to send these n little men into those n different houses. The input is a map of the scenario, a ‘.’ means an empty space, an ‘H’ represents a house on that point, and am ‘m’ indicates there is a little man on that point.

You can think of each point on the grid map as a quite large square, so it can hold n little men at the same time; also, it is okay if a little man steps on a grid with a house without entering that house.
Input
There are one or more test cases in the input. Each case starts with a line giving two integers N and M, where N is the number of rows of the map, and M is the number of columns. The rest of the input will be N lines describing the map. You may assume both N and M are between 2 and 100, inclusive. There will be the same number of ‘H’s and ‘m’s on the map; and there will be at most 100 houses. Input will terminate with 0 0 for N and M.
Output
For each test case, output one line with the single integer, which is the minimum amount, in dollars, you need to pay.
Sample Input
2 2
.m
H.
5 5
HH..m
…..
…..
…..
mm..H
7 8
…H….
…H….
…H….
mmmHmmmm
…H….
…H….
…H….
0 0
Sample Output
2
10
28

分析

这是一个最小权完备匹配,而KM算法是求最大权完备匹配,怎么办呢?其实,我们只需将二分图所有的边权取反,这样就变成求最大权的完备匹配了。
最后输出答案时再取反即可。

代码

#include<cstdio>#include<algorithm>using namespace std;#include<cstring>#define maxn 101int n,m,q,w[maxn][maxn],c[maxn],a[maxn],b[maxn],detal,t,tot;bool vx[maxn],vy[maxn];char o[maxn+5];struct node{    int x,y;    node(){}    node(int a,int b)    {        x=a,y=b;    }}man[maxn];struct point{    int x,y;    point(){}    point(int a,int b)    {        x=a,y=b;    }}home[maxn];bool dfs(int x){    vx[x]=1;    for(int i=1;i<=n;i++)        if(!vy[i])        {            t=a[x]+b[i]-w[x][i];            if(!t)            {                vy[i]=1;                if(!c[i]||dfs(c[i]))                {                    c[i]=x;                    return 1;                }            }            else detal=min(detal,t);        }    return 0;}int km(){    int ans=0;    for(int i=1;i<=n;i++)    {        while(1)        {            memset(vx,0,sizeof vx);            memset(vy,0,sizeof vy);            detal=(1<<30);            if(dfs(i)) break;            for(int j=1;j<=n;j++)                if(vx[j]) a[j]-=detal;            for(int j=1;j<=n;j++)                if(vy[j]) b[j]+=detal;        }    }    for(int i=1;i<=n;i++)        ans+=a[i]+b[i];    return ans;}int main(){    scanf("%d%d",&q,&m);    for(int i=1;i<=q;i++)    {        scanf("%s",o+1);        for(int j=1;j<=m;j++)            if(o[j]=='m') man[++n]=node(i,j);            else if(o[j]=='H') home[++tot]=point(i,j);    }    for(int i=1;i<=n;i++)    {        a[i]=-(1<<30);        for(int j=1;j<=n;j++)        {            w[i][j]=abs(man[i].x-home[j].x)+abs(man[i].y-home[j].y);            w[i][j]=-w[i][j];            a[i]=max(a[i],w[i][j]);        }    }    printf("%d",-km());    memset(a,0,sizeof a);    memset(b,0,sizeof b);    memset(c,0,sizeof c);    n=tot=0;}
原创粉丝点击