题意:
给出一个n*m的图,其中m是人,H是房子,每个人移动一步需要一块,问所有人移动到房子里的最少花费。
建图:
建立一个超级源点和超级汇点,S=0,T=2*num+1。
从源点到所有的人建立一条流,容量cap[S][i]=1,费用cost[S][i]=0;
从房子到汇点建立一条流,容量cap[j][T]=1,费用cost[j][T]=0;
建立所有人到所有房子的流,cap[i][j]=1,cost[i][j]为两个坐标的差值。
然后求一次费用流即可。
#include <iostream> #include <cstdio> #include <algorithm> #include <string> #include <cmath> #include <cstring> #include <queue> #include <set> #include <vector> #include <stack> #include <map> #include <iomanip> #define PI acos(-1.0) #define Max 5005 #define inf 1<<28 #define LL(x) (x<<1) #define RR(x) (x<<1|1) using namespace std; struct kdq { int x,y; }human[Max],house[Max]; int n,m; int S,T;//源点,汇点 int cost[Max/10][Max];//花费 int cap[Max/10][Max];//容量 int dis[Max]; int path[Max]; bool visit[Max]; int q[Max*10]; int spfa()//最短路 { int i,j; for(i=0;i<=T;i++) dis[i]=inf,path[i]=-1,visit[i]=0; dis[S]=0; visit[S]=1; int num=0,cnt=0; q[num++]=S; while(num>cnt) { int temp=q[cnt++]; visit[temp]=0; for(i=0;i<=T;i++) { if(cap[temp][i]&&dis[temp]+cost[temp][i]<dis[i]) { path[i]=temp; dis[i]=dis[temp]+cost[temp][i]; if(!visit[i]) { q[num++]=i; visit[i]=1; } } } } return dis[T]!=inf; } int minCost=0; void getMaxFlow()//增广找最大流 { int maxFlow=inf; while(spfa()) { int pre=T; while(path[pre]!=-1) { maxFlow=min(maxFlow,cap[path[pre]][pre]); pre=path[pre]; } pre=T; minCost+=dis[T]*maxFlow;//最小费用 while(path[pre]!=-1)//更新流 { cap[path[pre]][pre]-=maxFlow; cap[pre][path[pre]]+=maxFlow; //minCost+=cost[path[pre]][pre]*maxFlow; pre=path[pre]; } } cout<<minCost<<endl; return ; } int getdis(kdq x,kdq y)//两个坐标之间的费用 { return (abs(x.x-y.x)+abs(y.y-x.y)); } void build_map(int numm,int numh)//建图 { int i,j; for(i=1;i<=numm;i++)//计算房子和人之间的费用 for(j=1;j<=numh;j++){ cost[i][j+numm]=getdis(human[i],house[j]); cost[j+numm][i]=-cost[i][j+numm];//负费用用来回流 } for(i=1;i<=numm;i++)//源点到每个人的cap,cost cap[S][i]=1,cost[S][i]=0; for(i=1;i<=numh;i++)//房子到汇点的cap,cost cap[i+numm][T]=1; for(i=1;i<=numm;i++)//每个人和房子之间的cap for(j=1;j<=numh;j++) cap[i][j+numm]=1; } int main() { int i,j,k,l; char x; while(scanf("%d%d",&n,&m),(n+m)) { memset(cap,0,sizeof(cap)); memset(cost,0,sizeof(cost)); int numm=0,numh=0; for(i=1;i<=n;i++) for(j=1;j<=m;j++) { cin>>x; if(x=='m'){ human[++numm].x=i; human[numm].y=j; } if(x=='H'){ house[++numh].x=i; house[numh].y=j; } } S=0; minCost=0; T=numm+numh+1;//其实numm==numh。。。。 build_map(numm,numh); getMaxFlow(); } return 0; }
(转)KM算法,同样可以解此题。留下来看看
这是一个典型的最大匹配的题目,题目意思是给出一些房子和一些人,每个人到每个房子都有一个相应的代价,最后要求怎么安排这些人,房子和人一一配对,使最后的代价最小。
方法是KM算法,是一个求最大(最小)匹配的一个很强大的算法。不过这种题目还可以用费用流来做。
下面是某牛的对KM算法讲解
http://hi.baidu.com/anonympine/blog/item/3ee64954fe6f6256574e0021.html
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,那么我们会发现:
以上就是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。
不是特别理解这个算法,但是代码是照着大牛的写出来了.以后再研究吧!!!
#include <iostream> #include <cstdio> #include <algorithm> #include <string> #include <cmath> #include <cstring> #include <queue> #include <set> #include <vector> #include <stack> #include <map> #include <iomanip> #define PI acos(-1.0) #define Max 2005 #define inf 1<<28 #define LL(x) (x<<1) #define RR(x) (x<<1|1) using namespace std; int n,m; struct kdq { int x,y; void k(int xx,int yy) { x=xx,y=yy; } int diss(const kdq &aa) { return abs(x-aa.x)+abs(y-aa.y); } }; kdq a[Max],b[Max]; int x[Max],y[Max]; int match[Max]; int dis[Max][Max]; int km1[Max],km2[Max]; int _man,_house; int d; int dfs(int cur) { x[cur]=1; for(int i=0; i<_man; i++) { if(!y[i]) { int t=km1[cur]+km2[i]-dis[cur][i]; t=-t; if(!t) { y[i]=1; if(match[i]==-1||dfs(match[i])) { match[i]=cur; return 1; } } else if(d>t) d=t; } } return 0; } void km() { for(int i=0; i<_man; i++) { while(1) { memset(x,0,sizeof(x)); memset(y,0,sizeof(y)); d=inf; if(dfs(i)) break; for(int j=0; j<_man; j++) { if(x[j])km1[j]+=d;//最大权做相反处理 if(y[j])km2[j]-=d; } } } } int main() { int i,j,k,l; while(scanf("%d%d",&n,&m),(n+m)) { char xx; _man=_house=0; memset(match,-1,sizeof(match)); memset(km2,0,sizeof(km2)); for(i=0; i<=n; i++) km1[i]=inf;//最小权,最大权则赋值成0; for(i=1; i<=n; i++) { for(j=1; j<=m; j++) { cin>>xx; if(xx=='m') { a[_man++].k(i,j); } if(xx=='H') { b[_house++].k(i,j); } } } for(i=0; i<_man; i++) { for(j=0; j<_house; j++) { dis[i][j]=a[i].diss(b[j]); if(km1[i]>dis[i][j]) km1[i]=dis[i][j]; } } km(); int ans=0; for(i=0; i<_man; i++) ans+=(km1[i]+km2[i]); cout<<ans<<endl; } return 0; }