hdu 1553 Going Home【最大流最小費用流】
阿新 • • 發佈:2018-12-19
題意:給一個 n*m 的地圖,上面有數量相同的人和房子,人每走一格需要花費一塊前,問全部人走到房子裡的最小花費;
思路:用網路流 最大流最小費用流求解,讓全部人連線超級源點,全部房子連線超級匯點,邊的容量唯 1 ,花費可以用bfs求;
#include<cstring> #include<string> #include<cstdio> #include<stdlib.h> #include<iostream> #include<algorithm> #include<math.h> #include<map> #include<vector> #include<stack> #define inf 0x3f3f3f3f #include<queue> #include<set> using namespace std; typedef long long ll; const int N=205; struct nod { int x,y,s; }; struct node { int v,f,cos,ne; }edge[N*N*2]; int head[N],pre[N],dist[N];//head[]為每個變得下標,pre[]用來記錄前驅,dist[]最短路時的距離陣列 bool spaf_vis[N];//最短路的標記陣列 int s,t,e; char mp[N][N]; int n,m; int size; int coss[N],num[N];//coss[]存人到每個房子的花費,num[]記錄編號 int mark[N][N],vis[N][N];//mark[]是bfs的標記陣列,vis[]是記錄房子的編號 int dis[4][2]= {0,1,1,0,0,-1,-1,0};//方向陣列 void bfs(int x,int y)//找出人到房子的花費 { queue<nod>que; nod q1,q2; q1.x=x; q1.y=y; q1.s=0; que.push(q1); memset(mark,0,sizeof(mark)); mark[x][y]=1; while(!que.empty()) { nod q1=que.front(); que.pop(); for(int i=0; i<4; i++) { q2.x=q1.x+dis[i][0]; q2.y=q1.y+dis[i][1]; if(q2.x<n&&q2.x>=0&&q2.y>=0&&q2.y<m&&mark[q2.x][q2.y]==0) { q2.s=q1.s+1; mark[q2.x][q2.y]=1; if(mp[q2.x][q2.y]=='H') { coss[size]=q2.s; num[size++]=vis[q2.x][q2.y]; } que.push(q2); } } } } void add_insert(int a,int b,int c,int d) { edge[e].v=b; edge[e].f=c; edge[e].cos=d; edge[e].ne=head[a]; head[a]=e++; } void add(int a,int b,int c,int d) { add_insert(a,b,c,d); add_insert(b,a,0,-d); } int spaf()//尋找花費最少的路徑 { //跑一遍SPFA 找s——t的最少花銷路徑 且該路徑上每一條邊不能滿流 //若存在 說明可以繼續增廣,反之不能 memset(spaf_vis,0,sizeof(spaf_vis)); memset(dist,inf,sizeof(dist)); memset(pre,-1,sizeof(pre)); queue<int>que; spaf_vis[s]=1; dist[s]=0; que.push(s); while(!que.empty()) { int u=que.front(); que.pop(); spaf_vis[u]=0; for(int i=head[u];i!=-1;i=edge[i].ne) { int v=edge[i].v,f=edge[i].f,c=edge[i].cos; if(dist[v]>dist[u]+c&&f) { dist[v]=dist[u]+c; pre[v]=i;//記錄前驅的下標 if(!spaf_vis[v]) { spaf_vis[v]=1; que.push(v); } } } } return pre[t]!=-1; } void MCMF() { int flow=0,cost=0;//總容量和總花費 while(spaf())//尋找花銷最小的路徑 { int mini=inf; //通過反向弧 在源點到匯點的最少花費路徑 找最小增廣流 for(int i=pre[t];i!=-1;i=pre[edge[i^1].v]) mini=min(mini,edge[i].f); for(int i=pre[t];i!=-1;i=pre[edge[i^1].v]) { edge[i].f-=mini; edge[i^1].f+=mini; cost+=edge[i].cos*mini; } flow+=mini; } printf("%d\n",cost); } int main() { while(~scanf("%d %d",&n,&m)) { if(n==0&&m==0) break; int home=0,man=0;e=0; memset(coss,0,sizeof(coss)); memset(vis,0,sizeof(vis)); memset(head,-1,sizeof(head)); for(int i=0; i<n; i++) scanf("%s",mp[i]); for(int i=0; i<n; i++) { for(int j=0; j<m; j++) if(mp[i][j]=='H') vis[i][j]=++home; else if(mp[i][j]=='m') man++; } int temp=0; for(int i=0; i<n; i++) { for(int j=0; j<m; j++) { size=0; if(mp[i][j]=='m') { temp++; bfs(i,j); for(int k=0;k<size;k++) add(temp,man+num[k],1,coss[k]); } } } s=0;t=home+man+1; for(int i=1;i<=man;i++) add(0,i,1,0); for(int i=1;i<=home;i++) add(man+i,t,1,0); MCMF(); } return 0; }