二分圖KM演算法 POJ 2195
阿新 • • 發佈:2019-02-13
暴力(n^3)
#include<cstdio> #include<cmath> #include<cstring> #include<algorithm> using namespace std; #define MAX 105 #define INF 9999999 struct House { int r, c; } house[MAX]; struct Man { int r, c; } man[MAX]; int H, M, n, m; int A[MAX], B[MAX]; int visA[MAX], visB[MAX]; int match[MAX], slack[MAX], map[MAX][MAX]; bool find_path ( int i ) { visA[i] = true; for ( int j = 0; j < H; j++ ) { if ( !visB[j] && A[i] + B[j] == map[i][j] ) { visB[j] = true; if (match[j] == -1 || find_path(match[j])) { match[j] = i; return true; } } else if ( A[i] + B[j] > map[i][j] ) //j屬於B,且不在交錯路徑中 slack[j] = min(slack[j], A[i]+B[j]-map[i][j]); } return false; } void KM () { int i, j, d; memset(A,0,sizeof(A)); memset(B,0,sizeof(B)); memset(match,-1,sizeof(match)); for ( i = 0; i < M; i++ ) for ( j = 0; j < H; j++ ) A[i] = max (map[i][j], A[i]); for ( i = 0; i < M; i++ ) { for ( j = 0; j < H; j++ ) slack[j] = INF; while ( 1 ) { memset(visA,0,sizeof(visA)); memset(visB,0,sizeof(visB)); if ( find_path ( i ) ) break; //從i點出發找到交錯路徑則跳出迴圈 for ( d = INF, j = 0; j < H; j++ ) //取最小的slack[j] if (!visB[j] && d > slack[j]) d = slack[j]; for ( j = 0; j < M; j++ ) //集合A中位於交錯路徑上的-d if ( visA[j] ) A[j] -= d; for ( j = 0; j < H; j++ ) //集合B中位於交錯路徑上的+d if ( visB[j] ) B[j] += d; else slack[j] -= d; //注意修改不在交錯路徑上的slack[j] } } } int main() { char s[MAX]; int i, j, res; while ( scanf("%d%d",&n,&m) ) { if ( !m && !n ) break; H = M = res = 0; for ( i = 0; i < n; i++ ) { scanf("%s",s); for ( j = 0; j < m; j++ ) { if ( s[j] == 'H' ) house[H].r = i, house[H++].c = j; else if ( s[j] == 'm' ) man[M].r = i, man[M++].c = j; } } for ( i = 0; i < M; i++ ) //求最小帶權匹配可以將權值改為負數 for ( j = 0; j < H; j++ ) map[i][j] = -(abs(man[i].r-house[j].r) + abs(man[i].c-house[j].c)); KM(); for ( j = 0; j < H; j++ ) res -= map[match[j]][j]; printf("%d\n",res); } return 0; }
KM演算法
#include <stdio.h> #include <string.h> #include <algorithm> using namespace std; const int maxn = 105; const int INF = 1<<29; int w[maxn][maxn]; int n; int ly[maxn],lx[maxn];//頂標 bool S[maxn],T[maxn]; //左/右第i個點是否已經標記 int left[maxn]; //匹配為右邊第i個點的編號 int H, M; int R,C; bool match(int i){ S[i]=1; for(int j=1;j<=n;j++) if(lx[i]+ly[j]==w[i][j]&&!T[j]){ T[j]=1; if(!left[j]||match(left[j])){ left[j]=i; return true; } } return false; } void update(){ int a=INF; for(int i=1;i<=n;i++){ if(S[i]) for(int j=1;j<=n;j++){ if(!T[j]) a=min(a,lx[i]+ly[j]-w[i][j]); } } for(int i=1;i<=n;i++){ if(S[i]) lx[i]-=a; if(T[i]) ly[i]+=a; } } void KM(){ for(int i=1;i<=n;i++){ left[i]=lx[i]=ly[i]=0; for(int j=1; j<=n;j++){ lx[i]=max(lx[i],w[i][j]); } } for(int i=1;i<=n;i++){ while(1){ //printf("1"); memset(T,0,sizeof(T)); memset(S,0,sizeof(S)); if(match(i)) break; else update(); } } } struct House { int r, c; } house[maxn]; struct Man { int r, c; } man[maxn]; int main() { char s[maxn]; int i, j; while ( scanf("%d %d",&R,&C) ) { if ( !R && !C ) break; H = M = 1; for ( i = 1; i <=R; i++ ) { scanf("%s",s+1); for ( j = 1; j <=C; j++ ) { if ( s[j] == 'H' ) house[H].r = i, house[H++].c = j; else if ( s[j] == 'm' ) man[M].r = i, man[M++].c = j; } } for ( i = 1; i <= M-1; i++ ) //求最小帶權匹配可以將權值改為負數 for ( j = 1; j <= H-1; j++ ) w[i][j] = -(abs(man[i].r-house[j].r) + abs(man[i].c-house[j].c)); n=H-1; KM(); int res=0; for ( j = 1; j <=H-1; j++ ) res -= w[left[j]][j]; printf("%d\n",res); } return 0; }