最小费用流第二题,貌似比第一题简单啊……
(这道题也可以用二分图的最优匹配来解。下面有KM算法的解法。)
题目大意:
在一个地图上给出房子的位置和人的位置,人和房子的数量是相等的。人要回到房子里,每个房子只能回一个人。人向房子每移动一个单位需要花费$1,求人全部回房子的最小花费。
注意事项 :
因为地图上的人和房子数不一定 ,最大可能是10000个点都用上,所以我就这么试了一下,结果MLE了,后来经尝试不超过1005个点,因为我开的数组大小是1005的。
下面是代码:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <queue> using namespace std; const int inf=1<<30,M=1005; int n,m; short c[M][M],f[M][M],w[M][M]; int dis[M],pre[M]; bool vis[M]; char s[105][105]; struct node { int x,y; } point[M]; int min(int a,int b) { if(a>b) { a=b; } return a; } bool spfa() { int i,j; for(i=0; i<=n+m+1; i++) //初始化 { dis[i]=inf; pre[i]=-1; vis[i]=false; } dis[0]=0; vis[0]=true; queue <int> q; q.push(0); while(!q.empty()) { int t=q.front(); q.pop(); vis[t]=false; for(i=1; i<=n+m+1; i++) { if(c[t][i]>f[t][i]&&dis[i]>dis[t]+w[t][i]) //如果流量没有到最大且超级原点经过点t到i的费用比直接到i小 { dis[i]=dis[t]+w[t][i]; //更新到i点的最小花费 pre[i]=t; //更新最短路径中点i的前驱为t if(!vis[i]) //如果点i没在队列中 { q.push(i); //将点i放入队列 vis[i]=true; //标记已在队列中 } } } } if(pre[n+m+1]==-1)//如果超级汇点没有在对短路中 (因为没有前驱) { return false;//返回寻找最短路失败 } return true; //返回最短路寻找成功 } int main() { int n1,m1; while(scanf("%d%d",&n1,&m1),n1||m1) { int i,j; n=1; m=0; for(i=0; i<n1; i++) { scanf("%s",s[i]); for(j=0; j<m1; j++) { if(s[i][j]=='m') { point[n].x=i; point[n].y=j; n++; } } } for(i=0; i<n1; i++) { for(j=0; j<m1; j++) { if(s[i][j]=='H') { point[n+m].x=i; point[n+m].y=j; m++; } } } memset(c,0,sizeof(c)); memset(f,0,sizeof(f)); memset(w,0,sizeof(w)); n--; for(i=1; i<=n; i++) { for(j=1; j<=m; j++) { w[i][n+j]=abs(point[i].x-point[n+j].x)+abs(point[i].y-point[n+j].y); w[n+j][i]=-w[i][n+j]; } } for(i=1; i<=n; i++) { c[0][i]=1; } for(i=n+1; i<=n+m; i++) { c[i][n+m+1]=1; } for(i=1; i<=n; i++) { for(j=1; j<=m; j++) { c[i][n+j]=1; } } while(spfa()) { int maxflow=inf;//初始化为最大值 int p=n+m+1; while(pre[p]!=-1) //遍历最小费用增广路 { maxflow=min(maxflow,c[pre[p]][p]-f[pre[p]][p]);//寻找关键流量,及最短路上的最小流量 p=pre[p]; } p=n+m+1; //再次初始化; while(pre[p]!=-1) //再次遍历最小费用增广路 { f[pre[p]][p]+=maxflow; f[p][pre[p]]=-f[pre[p]][p]; //调整流量 p=pre[p]; } } int ans=0,d; for(j=1; j<=n; j++) { for(d=1; d<=m; d++) { ans+=f[j][d+n]*w[j][d+n];//计算总费用 } } printf("%d\n",ans); } return 0; }
这道题也可以用二分图的最优匹配(KM算法来做),就简单了(2014年2月13号修改)。
上面是KM算法的情况,下面是最小费用最大流的情况。
可见在这个题上,KM算法还是有优势的。
下面是代码:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <iostream> using namespace std; const int Max=2005; const int inf=1<<28; struct node { int x,y; void k(int xx,int yy) { x=xx,y=yy; } int diss(const node &aa) { return abs(x-aa.x)+abs(y-aa.y); } } a[Max],b[Max]; int d,n,m,cnta,cntb,pre[Max],dis[Max][Max],km1[Max],km2[Max]; bool x[Max],y[Max]; void init() { cnta=0; cntb=0; memset(pre,-1,sizeof(pre)); memset(km2,0,sizeof(km2)); for(int i=0; i<=n; i++) { km1[i]=inf; } } bool dfs(int src) { x[src]=true; for(int i=0; i<cnta; i++) { if(!y[i]) { int t = km1[src]+km2[i]-dis[src][i]; t=-t; if(!t) { y[i]=true; if(pre[i]==-1||dfs(pre[i])) { pre[i]=src; return true; } } else if(d>t)d=t; } } return false; } void km() { for(int i=0; i<cnta; i++) { while(1) { memset(x,false,sizeof(x)); memset(y,false,sizeof(y)); d=inf; if(dfs(i))break; for(int j=0; j<cnta; j++) { if(x[j])km1[j]+=d; if(y[j])km2[j]-=d; } } } } int main() { while(scanf("%d%d",&n,&m),n||m) { char s[105]; init(); for(int i=1 ; i<=n; i++) { cin >>s; for(int j=1; j<=m; j++ ) { if(s[j-1]=='m')a[cnta++].k(i,j); else if(s[j-1]=='H')b[cntb++].k(i,j); } } for(int i=0; i<cnta; i++) { for(int j=0; j<cntb; 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(int i=0; i<cnta; i++) { ans+=(km1[i]+km2[i]); } printf("%d\n",ans); } return 0; }