题目:http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=31284
Time Limit: 1000MS | Memory Limit: 32768KB | 64bit IO Format: %I64d & %I64u |
Description
传说在遥远的地方有一个非常富裕的村落,有一天,村长决定进行制度改革:重新分配房子。
这可是一件大事,关系到人民的住房问题啊。村里共有n间房间,刚好有n家老百姓,考虑到每家都要有房住(如果有老百姓没房子住的话,容易引起不安定因素),每家必须分配到一间房子且只能得到一间房子。
另一方面,村长和另外的村领导希望得到最大的效益,这样村里的机构才会有钱.由于老百姓都比较富裕,他们都能对每一间房子在他们的经济范围内出一定的价格,比如有3间房子,一家老百姓可以对第一间出10万,对第2间出2万,对第3间出20万.(当然是在他们的经济范围内).现在这个问题就是村领导怎样分配房子才能使收入最大.(村民即使有钱购买一间房子但不一定能买到,要看村领导分配的).
这可是一件大事,关系到人民的住房问题啊。村里共有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
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,那么我们会发现:
1)两端都在交错树中的边(i,j),A[ i ]+B[j]的值没有变化。也就是说,它原来属于相等子图,现在仍属于相等子图。
2)两端都不在交错树中的边(i,j),A[ i ]和B[j]都没有变化。也就是说,它原来属于(或不属于)相等子图,现在仍属于(或不属于)相等子图。
3)X端不在交错树中,Y端在交错树中的边(i,j),它的A[ i ]+B[j]的值有所增大。它原来不属于相等子图,现在仍不属于相等子图。
4)X端在交错树中,Y端不在交错树中的边(i,j),它的A[ i ]+B[j]的值有所减小。也就说,它原来不属于相等子图,现在可能进入了相等子图,因而使相等子图得到了扩大。
5)到最后,X端每个点至少有一条线连着,Y端每个点有一条线连着,说明最后补充完的相等子图一定有完备匹配。(若由二分图中所有满足A[ i ]+B[j]=w[i,j]的边(i,j)构成的子图(称做相等子图)有完备匹配,那么这个完备匹配就是二分图的最大权匹配。)现在的问题就是求d值了。为了使A[ i ]+B[j]>=w[i,j]始终成立,且至少有一条边进入相等子图,d应该等于:Min{A[ i ]+B[j]-w[i,j] | Xi在交错树中,Yi不在交错树中}。
KM算法的复杂度是可以做到O(n^3)的。我们给每个Y顶点一个“松弛量”函数slack,每次开始找增广路时初始化为无穷大。在寻找增广路的过程中,检查边(i,j)时,如果它不在相等子图中,则让slack[j]变成原值与A[ i ]+B[j]-w[i,j]的较小值。这样,在修改顶标时,取所有不在交错树中的Y顶点的slack值中的最小值作为d值即可。但还要注意一点:修改顶标后,要把所有的不在交错树中的Y顶点的slack值都减去d。
Kuhn-Munkras算法流程:
(1)初始化可行顶标的值;
(2)用匈牙利算法寻找完备匹配;
(3)若未找到完备匹配则修改可行顶标的值;
(4)重复(2)(3)直到找到相等子图的完备匹配为止;
附上几张盗来的ppt图片,加深印象:
本题代码:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int M=310,inf=0x3f3f3f3f;
int n,nx,ny;
int link[M],lx[M],ly[M],slack[M];//lx,ly为顶标,nx,ny分别为x点集y点集的个数
int visx[M],visy[M],w[M][M];
int DFS(int x)
{
visx[x] = 1;
for (int y = 1; y <= ny; y ++)
{
if (visy[y]) continue;
int t = lx[x] + ly[y] - w[x][y];
if (t == 0)
{
visy[y] = 1;
if (link[y] == -1||DFS(link[y]))
{
link[y] = x;
return 1;
}
}
else if (slack[y] > t) //不在相等子图中slack 取最小的
slack[y] = t;
}
return 0;
}
int KM()
{
int i,j;
memset (link,-1,sizeof(link));
memset (ly,0,sizeof(ly));
for (i = 1; i <= nx; i ++) //lx初始化为与它关联边中最大的
for (j = 1,lx[i] = -inf; j <= ny; j ++)
if (w[i][j] > lx[i])
lx[i] = w[i][j];
for (int x = 1; x <= nx; x ++)
{
for (i = 1; i <= ny; i ++)
slack[i] = inf;
while (1)
{
memset (visx,0,sizeof(visx));
memset (visy,0,sizeof(visy));
if (DFS(x)) break;
int d = inf;
for (i = 1; i <= ny; i ++)
if (!visy[i]&&d > slack[i])
d = slack[i];
for (i = 1; i <= nx; i ++)
if (visx[i])
lx[i] -= d;
for (i = 1; i <= ny; i ++)
if (visy[i])
ly[i] += d;
else
slack[i] -= d;
}
}
int res = 0;
for (i = 1; i <= ny; i ++)
if (link[i] > -1)
res += w[link[i]][i];
return res;
}
int main()
{
//freopen("cin.txt","r",stdin);
int n,val;
while(cin>>n){
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
scanf("%d",&val);
w[i][j]=val;
}
}
nx=ny=n;
printf("%d\n",KM());
}
return 0;
}