/* 题意:给定一个N*M的地图,地图上有若干个man和house,且man与house的数量一致。man每移动一格需花费$1(即单位费用=单位距离), 一间house只能入住一个man。现在要求所有的man都入住house,求最小费用。 最小费用最大流 用到了spfa求费用最小的可改进路径 仿写 http://blog.csdn.net/lyy289065406/article/details/6732762 这个的,这儿有题解和注释,几乎完全一样,哈哈,学习了。 */ #include<iostream> #include<cmath> #include<queue> using namespace std; class coordinate { public: int x,y; coordinate() {} }; class solve { public: solve(int row,int col):r(row),c(col) { int i,j,mzz,hzz; mannum=0; MinCost=0; map=new char*[r]; for(i=0;i<r;i++) { map[i]=new char[c]; for(j=0;j<c;j++) { cin>>map[i][j]; if(map[i][j]=='m') ++mannum; } } //找到人和家的坐标 man=new coordinate[mannum+1]; house=new coordinate[mannum+1]; mzz=hzz=0; for(i=0;i<r;++i) for(j=0;j<c;++j) if(map[i][j]=='m') { man[++mzz].x=i; man[mzz].y=j; }else if(map[i][j]=='H') { house[++hzz].x=i; house[hzz].y=j; } //建 花费图 和 流图 cost=new int*[2*mannum+2]; flow=new int*[2*mannum+2]; for(i=0;i<2*mannum+2;++i) { cost[i]=new int[2*mannum+2]; flow[i]=new int[2*mannum+2]; memset(cost[i],0,sizeof(int)*(2*mannum+2));//注意哦 跟以往的写法不太一样 memset(flow[i],0,sizeof(int)*(2*mannum+2)); } //初始化 s=0; t=2*mannum+1; for(i=1;i<=mannum;++i) flow[s][i]=1; for(i=1;i<=mannum;++i) for(j=1;j<=mannum;++j) { cost[i][j+mannum]=abs(man[i].x-house[j].x)+abs(man[i].y-house[j].y); cost[j+mannum][i]=-cost[i][j+mannum]; flow[i][j+mannum]=1; } for(i=mannum+1;i<t;++i) flow[i][t]=1; //求解 pre=new int[2*mannum+2]; memset(pre,0,sizeof(int)*(2*mannum+2)); dist=new int[2*mannum+2]; vis=new int[2*mannum+2]; while(spfa())//还有增广路径 add();//压入流 } int inf() const{return 0x7FFFFFFF;}//设最大值 int min(int a,int b) {return a<b?a:b;}//取较小的 int spfa()//求费用最小的改进路径 { dist[s]=0; for(int i=1;i<2*mannum+2;i++) dist[i]=inf(); memset(vis,0,sizeof(int)*(2*mannum+2)); vis[s]=1; queue<int>q; q.push(s); while(!q.empty()) { int u=q.front(); for(int v=0;v<=t;++v) { if(flow[u][v]&&dist[v]>dist[u]+cost[u][v]) { dist[v]=dist[u]+cost[u][v]; pre[v]=u; if(!vis[v]) { q.push(v); vis[v]=1; } } } q.pop(); vis[u]=0; } if(dist[t]==inf()) return 0; return 1; } void add()//压入流 { int MaxFlow=inf(); int i; for(i=t;i!=s;i=pre[i]) MaxFlow=min(MaxFlow,flow[pre[i]][i]); for(i=t;i!=s;i=pre[i]) { flow[pre[i]][i]-=MaxFlow; flow[i][pre[i]]+=MaxFlow; MinCost+=cost[pre[i]][i]*MaxFlow; } return; } ~solve()// { cout<<MinCost<<endl;//输出解 delete[] man;//收回空间 delete[] house; delete[] dist; delete[] vis; delete[] pre; for(int i=0;i<r;++i) delete[] map[i]; for(int j=0;j<2*mannum+2;++j) { delete[] cost[j]; delete[] flow[j]; } delete[] map; delete[] cost; delete[] flow; } private: int MinCost; int r,c; int** cost,**flow; int mannum; char** map; coordinate *man; coordinate *house; int s,t; int* pre,*vis,*dist; }; int main() { int row,col; while(cin>>row>>col,row+col) solve poj2195(row,col); return 0; }
在求最短路的时候,需要判断 是否还有流可加,更新距离的时候依据的是cost
这题也可以用最小权匹配来做,学了二分图之后,用最小权匹配又写了一遍
/* poj 2195 Going Home 学习了二分图匹配,所以又用 最小权匹配 些了一边,比 最小费用最大流 快了一些 最小权匹配 与最大权匹配差不多 只需要把 边的权取相反数 然后过程跟最大权匹配一样 最后 边权和 是一个负数 再取相反数即可 http://blog.csdn.net/qq172108805/article/details/7855113 有讲解 */ #include<iostream> #include<cmath> using namespace std; int m,n; struct node//表示人或房子位置 { int x,y; }; struct edge//用邻接表存边 { int v,f,next; }e[100000]; node *man,*house;//人和房子的数组 int *match,*lx,*ly,*slack,*visx,*visy,*head,nman;//匹配 x的顶标 y的顶标 优化数组 x访问标志 y访问标志 邻接表头节点 人的数量 char map[110][110]; int hungray(int i)//匈牙利算法 { int j,v; visx[i]=1; for(j=head[i];j!=-1;j=e[j].next) { v=e[j].v; if(visy[v]) continue; if(lx[i]+ly[v]==e[j].f) { visy[v]=1; if(match[v]==-1||hungray(match[v])) { match[v]=i; return 1; } }else if(slack[v]>lx[i]+ly[v]-e[j].f) slack[v]=lx[i]+ly[v]-e[j].f; } return 0; } int km()//km { memset(match,-1,sizeof(int)*nman); int i,j,d; memset(ly,0,sizeof(int)*nman); for(i=0;i<nman;++i) { for(j=0;j<nman;++j) slack[j]=0x7fffffff; while(1) { memset(visx,0,sizeof(int)*nman); memset(visy,0,sizeof(int)*nman); if(hungray(i)) break; d=0x7fffffff; for(j=0;j<nman;++j) if(!visy[j]&&slack[j]<d) d=slack[j]; for(j=0;j<nman;++j) { if(visx[j]) lx[j]-=d; if(visy[j]) ly[j]+=d; else slack[j]-=d; } } } d=0; for(i=0;i<nman;++i) d+=lx[i],d+=ly[i]; return -d;//取相反数 } int main() { int i,j,yong; while(cin>>n>>m,m+n) { nman=0;//初始化 yong=0; for(i=0;i<n;++i)//读数据 for(j=0;j<m;++j) { cin>>map[i][j]; if(map[i][j]=='m') nman++; } match=new int[nman];//申请空间 man=new node[nman];// house=new node[nman];// lx=new int[nman];// ly=new int[nman];// slack=new int[nman];// visx=new int[nman];// visy=new int[nman];// head=new int[nman];// int jman=0,jhouse=0; for(i=0;i<n;++i)//统计人和房子的位置 for(j=0;j<m;++j) { if(map[i][j]=='m') { man[jman].x=i; man[jman++].y=j; }else if(map[i][j]=='H') { house[jhouse].x=i; house[jhouse++].y=j; } } memset(head,-1,sizeof(int)*nman);//计算人和房子的距离 建边 for(i=0;i<nman;++i) { int max=-(0x7fffffff);//在计算的同时给lx数组赋值,就是和i相连的边的最大权 for(j=0;j<nman;++j) { e[yong].v=j; e[yong].f=-(abs(man[i].x-house[j].x)+abs(man[i].y-house[j].y));//因为求最小权匹配 所以用权的相反数 if(max<e[yong].f) max=e[yong].f;//更新max e[yong].next=head[i]; head[i]=yong++; } lx[i]=max;//给lx赋值 } cout<<km()<<endl;//计算 输出 delete[] match;//释放空间 delete[] man; delete[] house; delete[] lx; delete[] ly; delete[] slack; delete[] visx; delete[] visy; delete[] head; } return 0; }