Going Home
Time Limit: 1000MS |
|
Memory Limit: 65536K |
Total Submissions: 9246 |
|
Accepted: 4765 |
Description
On a grid map there are n little men and n houses. In each unit time, every little man can move one unit step, either horizontally, or vertically, to an adjacent point. For each little man, you need to pay a $1 travel fee for every step he moves, until he enters a house. The task is complicated with the restriction that each house can accommodate only one little man.
Your task is to compute the minimum amount of money you need to pay in order to send these n little men into those n different houses. The input is a map of the scenario, a '.' means an empty space, an 'H' represents a house on that point, and am 'm' indicates there is a little man on that point.
You can think of each point on the grid map as a quite large square, so it can hold n little men at the same time; also, it is okay if a little man steps on a grid with a house without entering that house.
Input
There are one or more test cases in the input. Each case starts with a line giving two integers N and M, where N is the number of rows of the map, and M is the number of columns. The rest of the input will be N lines describing the map. You may assume both N and M are between 2 and 100, inclusive. There will be the same number of 'H's and 'm's on the map; and there will be at most 100 houses. Input will terminate with 0 0 for N and M.
Output
For each test case, output one line with the single integer, which is the minimum amount, in dollars, you need to pay.
Sample Input
2 2
.m
H.
5 5
HH..m
.....
.....
.....
mm..H
7 8
...H....
...H....
...H....
mmmHmmmm
...H....
...H....
...H....
0 0
Sample Output
2
10
28
最小花費最大流解法:
/*題目大意:n個人到m個房子分布在在座標軸的不同位置,求解n個人到m間房子的最小花費(人可以上下左右移動,每移動一次就花費$1);題目解答:這是一道二分最大匹配問題,由於題目特殊性(n==m)最大匹配就是完備匹配.接下來就是轉化成二分圖的問題,:將n個人歸為X集合m個房屋歸為Y集合,增加一個源點和匯點,尋找一條最小花費的最大匹配:(可以採用隊列實現的bellman演算法(SPFA)尋找一條最短路的增廣路徑)代替dfs尋找的增廣路徑就ok了.代碼技巧:struct {i,j}記錄座標,便於後面計算距離(花費).dis[mi][mi+hi]就是第i個人到第hi個房子的距離(花費),:c[x][y]=0,1表示參與流量也因為都只有0和1.d[x]當前增廣路徑中源點S到x點的當前最短距離.*/Source CodeProblem: 2195 User: wawadimu Memory: 608K Time: 79MS Language: C++ Result: Accepted Source Code #include<iostream>#include<algorithm>#include<queue>using namespace std;#define inf INT_MAX#define maxn 110struct node{int i,j;//記錄座標}man[maxn],house[maxn];//dis[x][y]頂點x到頂點y的距離//c[x][y]表示當前殘餘流量值:1或者0//p[x]記錄增益路徑中x點前一個頂點//inq[x]標記函數//hi房子數目,mi人數//增益路徑中d[x]是S到x的最短距離int dis[2*maxn][2*maxn],c[2*maxn][2*maxn];int p[2*maxn];bool inq[2*maxn];int d[2*maxn];int hi,mi;//mi==hi//採用FIFO隊列Bellman_Ford尋找一條增益路徑,成功返回1,失敗返回0int bellman_ford(int s,int t){queue<int> q;q.push(s);//初始化標記函數和距離函數memset(inq,0,sizeof(inq));for(int i=0;i<=2*mi+1;i++) d[i]=inf;d[s]=0;inq[s]=1;while(!q.empty()){int x=q.front(); q.pop();inq[x]=false;for(int v=1;v<=2*mi+1;v++){if(d[v] > d[x] + dis[x][v] && c[x][v]) //有殘餘,鬆弛{d[v]=d[x]+dis[x][v];p[v]=x; //記錄路徑if(!inq[v]) //已經在隊列中就不需要重複添加{inq[v]=true;q.push(v);}}}}return d[2*mi+1]!=inf;//是否找到增益路徑}int main(){//freopen("2195.txt","r",stdin);//freopen("out1.txt","w",stdout);int n,m;int i,j;char ch;//int mi,hi;while(scanf("%d%d/n",&n,&m)!=EOF && !(n==0 && m==0)){mi=hi=1;//scanf("&c",&ch);for(i=1;i<=n;i++){for(j=1;j<=m;j++){//cin>>ch;scanf("%c/n",&ch);if(ch=='m'){man[mi].i = i;man[mi++].j = j;}if(ch=='H'){house[hi].i = i;house[hi++].j = j;}}}hi--;mi--;//初始化殘餘流量為0//dis[x][y] x,y不相連memset(c,0,sizeof(c));for(i=0;i<=2*mi+1;i++)for(j=0;j<=2*mi+1;j++)dis[i][j]=inf;for(i=1;i<=mi;i++){for(j=1;j<=hi;j++){//計算dis[man][house]=|i|+|j|;//計算c[man][house]=1;dis[i][mi+j]=abs(man[i].i-house[j].i) + abs(man[i].j - house[j].j);dis[mi+j][i]=-dis[i][mi+j];c[i][mi+j]=1;}}for(i=1;i<=mi;i++){//創造一個源點S[0]和匯點t[2*mi+1]//dis[0][man]=dis[man][0]=0 c[0][man]=1;//dis[t][house]=dis[house][t]=0 c[house][t]=1;dis[0][i]=dis[i][0]=0;dis[mi+i][2*mi+1]=dis[2*mi+1][mi+i]=0;c[0][i]=1;c[mi+i][2*mi+1]=1;}int s=0,t=2*mi+1;int min=0;//記錄費用while(bellman_ford(s,t)){for(int u=t;u!=s;u=p[u]){//cout<<u<<" ";c[p[u]][u]-=1;c[u][p[u]]+=1;}min+=d[2*mi+1];}cout<<min<<endl;}return 1;}
KM解法:
/*題目大意:略;題目解答:KM演算法。前提條件:加權完全二分圖(兩個子集的任意兩個頂點都相關聯)。相等子圖滿足lx(i)+ly(j)=w(i,j);:(1)擴充相等子圖G(l),在G(l)中尋找一條增廣路徑:首先將出發頂點s加入到想等子圖G(l)中,然後依次搜尋頂點s出發的每一條邊(s,j),如果s和j的頂標和等於權值:則j進去G(l),若j是未蓋點或存在j出發的增廣路徑,則(s,j)是匹配邊,退出:(2)調整下標:搜尋所有X集合中每個滿足下列條件:i屬於G(l),j不屬於G(l);:min=minmum(map[i][j]-lx[i]+lx[j]); lx[i]+=min && ly[j屬於G(l)]-=min;(對於已經匹配的頂點lx[x]+ly[y]==map[x][y],:也就是不影響原先匹配的相等子圖):在尋找增廣路徑時,KM演算法不斷的調整頂標,直到找到一條增廣路徑。由於匹配數目M的不斷增加,增廣過程的資訊都儲存。:每一次增廣過程最壞複雜度o(E),總的複雜度O(NE^2); Source CodeProblem: 2195 User: wawadimu Memory: 224K Time: 0MS Language: C++ Result: Accepted Source Code */#include<iostream>using namespace std;#define inf INT_MAX#define maxn 110struct point{int x,y;}man[maxn],house[maxn];int N,M;int m,h;int map[maxn][maxn];int match[maxn];//match[y]=x Y集合匹配的X集合下標int vx[maxn],vy[maxn];//標記數組int lx[maxn],ly[maxn];//定標bool dfs(int i)//深度優先遍曆尋找增益路徑{vx[i]=1;for(int j=1;j<=h;j++){if(!vy[j] && lx[i]+ly[j]== map[i][j]){vy[j]=1;if(match[j]==-1 || dfs(match[j])){match[j]=i;return true;}}}return false;}int main(){//freopen("2195.txt","r",stdin);int i,j,k;char ch;while(scanf("%d%d/n",&N,&M)!=EOF && !(N==0 && M==0)){m=h=0;for(i=1;i<=N;i++){for(j=1;j<=M;j++){scanf("%c",&ch);//cout<<ch;if(ch=='m'){man[++m].x=i;man[m].y=j;}else if(ch=='H'){house[++h].x=i;house[h].y=j;}scanf("/n");//過濾分行符號}//cout<<endl;}//memset(map,inf,sizeof(map));memset(match,-1,sizeof(match));for(i=1;i<=m;i++){for(j=1;j<=h;j++){map[i][j]=abs(man[i].x-house[j].x)+abs(man[i].y- house[j].y);}}for(i=1;i<=m;i++){lx[i]=inf;for(j=1;j<=h;j++){if(lx[i] > map[i][j]) lx[i]=map[i][j];}//cout<<lx[i]<<endl;}//最小權匹配memset(ly,0,sizeof(ly));int min;int cnt=0;for(i=1;i<=m;i++)//注意下標變數{while(1){memset(vx,0,sizeof(vx));memset(vy,0,sizeof(vy));if(dfs(i)) break;min=inf;for(j=1;j<=m;j++){if(vx[j]){for(k=1;k<=h;k++){if(!vy[k] && map[j][k] - lx[j]-ly[k] <min){min=map[j][k] - lx[j]-ly[k];}}}}for(j=1;j<=m;j++) if(vx[j]) lx[j]+=min;for(j=1;j<=h;j++) if(vy[j]) ly[j]-=min;}}int ans=0;for(i=1;i<=h;i++){ans+=map[match[i]][i];}printf("%d/n",ans);}return 0;}