POJ_2195_Going Home
题意:用'H','m','.'作出矩阵,'H'代表房子,'m'代表人,人一次只能水平或者垂直移动到相邻的点,问所有人一共走的步数的最小值。
分析:明显的求二分图最大权匹配。KM算法求得的是最大权匹配,而题中要求的是最小值,所以要将边的权值以其负值储存。
有一点需要注意:link数组(匹配数组)必须初始化为-1,如果初始化为0,则link[0]=0,则默认第0个人与第0个房子匹配,在执行匈牙利算法是就会出错,找到错误的增广路,第一次提交就错在这。
总结:基本能只用KM算法,熟练度还有待增强。
代码:
#include<iostream> #include<cstdio> #include<cmath> #include<cstring> #include<stdlib.h> using namespace std; #define Del(x,y) memset(x,y,sizeof(x)) #define N 105 struct Point { int x,y; }; Point house[N]; Point man[N]; int n,m; int cnth,cntm; int lx[N],ly[N],link[N],w[N][N]; bool S[N],T[N]; char grid[N]; int dis(Point a,Point b) { return (abs(a.x-b.x)+abs(a.y-b.y)); } void update() { int a=99999999; for(int i=0; i<cnth; i++) if(S[i]) for(int j=0; j<cntm; j++) if(!T[j]) a=min(a,lx[i]+ly[j]-w[i][j]); for(int i=0; i<cnth; i++) { if(S[i]) lx[i]-=a; if(T[i]) ly[i]+=a; } } bool match(int i) { S[i]=true; for(int j=0; j<cntm; j++) if(lx[i]+ly[j]-w[i][j]==0&&!T[j]) { T[j]=true; if(link[j]==-1||match(link[j])) { link[j]=i; return true; } } return false; } void KM() { Del(link,-1); ///必须初始化为-1,如果初始化为0,则link[0]=0,则默认第0个人与第0个房子匹配,在执行匈牙利算法是就会出错,找到错误的增广路 for(int i=0; i<cnth; i++) { lx[i]=ly[i]=0; for(int j=0; j<cntm; j++) lx[i]=max(lx[i],w[i][j]); } for(int i=0; i<cnth; i++) for(;;) { Del(S,0); Del(T,0); if(match(i)) break; else update(); } } int main() { while(~scanf("%d%d",&n,&m)&&n!=0) { cntm=cnth=0; for(int i=0; i<n; i++) { scanf("%s",grid); for(int j=0; j<m; j++) { if(grid[j]=='H') { house[cnth].x=i; house[cnth].y=j; cnth++; } if(grid[j]=='m') { man[cntm].x=i; man[cntm].y=j; cntm++; } } } for(int i=0; i<cnth; i++) for(int j=0; j<cntm; j++) w[i][j]=-dis(house[i],man[j]); KM(); int ans=0; for(int i=0; i<cntm; i++) ans+=dis(house[link[i]],man[i]); printf("%d\n",ans); } return 0; }