题目链接:http://poj.org/problem?id=2195
题意:地图上有相同数量的人和房屋,让所有人回到不同的房屋里,总的路程最小,路程定义为哈密顿距离。
昨天开始看了一下费用流,思想和最大流差不多,只是找增广路的时候找最小花费的路径来增广。
用最好理解的EK算法来看,EK中的BFS过程就是在找增广路,如果把这个过程变成找一条最花费小的增广路就可以了。
关于增广路的理解,由EK的BFS看来,在算法过程中,残留网络源点到汇点有路径可走,这条路就是增广路。
正因为如此,就有了找最小费用增广路的办法。在BFS算法中仅可以确定是否有路径,所以我们需要用最短路算法替代BFS,如果没有路径,距离为INF,如果有路径,算法保证了一定是最短路径。
注意最短路算法一定要可以处理负边,因为拆边的时候会有负花费出现,通常选用SPFA。
正确性如上。如果不是增广路算法,怎么写费用流还不清楚,还有很多要学习的啊。
对这题来说,可以用最优匹配做。最大流可以做最大匹配,费用流就可以做最优匹配。难的不是算法,依旧是建图难。
贴个代码。
#include<cstdio> #include<cstring> #include<queue> #include<vector> #include<iostream> #include<algorithm> #define pb push_back #define pii pair<int,int> #define LL long long int #define INF 0x7fffffff #define LLINF 0x7fffffffffffffff using namespace std; const int M = 10005; char maz[105][105]; struct eg{ int u, v, cap, cost; eg(){} eg(int a, int b, int c, int d){ u = a, v = b, cap = c, cost = d; }; }edg[M<<2]; int fir[M<<2], nex[M<<2]; int n, m, s, t, ecnt; void add(int a, int b, int c, int d){ edg[ecnt] = eg(a, b, c, d); nex[ecnt] = fir[a], fir[a] = ecnt++; edg[ecnt] = eg(b, a, 0, -d); nex[ecnt] = fir[b], fir[b] = ecnt++; } int pre[M], dis[M]; int spfa(int s, int t, int n){ //spfa求最小花费增广路 queue<int>q; bool vis[M]={0}; memset(pre, -1, sizeof(pre)); for(int i = 0; i <= t; ++i) dis[i] = INF; dis[s] = 0; vis[s] = 1; q.push(s); while( !q.empty() ){ int u = q.front(); q.pop(); vis[u] = 0; for(int k = fir[u]; k != -1; k = nex[k]){ int v = edg[k].v; if( edg[k].cap && dis[u] + edg[k].cost < dis[v]){ dis[v] = edg[k].cost + dis[u]; pre[v] = k; if(!vis[v]){ q.push(v); vis[v] = 1; } } } } return dis[t] != INF; } int mincostEK(int s, int t, int n){ int res = 0, minflow; while( spfa(s, t, n)){ minflow = INF; for(int k = pre[t]; k != -1; k = pre[edg[k].u]){ minflow = min(minflow, edg[k].cap); //找到增广路上的最大可增广量 } for(int k = pre[t]; k != -1; k = pre[edg[k].u]){ edg[k].cap -= minflow; edg[k^1].cap += minflow; //有人些会保存对应的相反边 其实每次加边的时候序号+2,只用取^1就可以了,之前用的矩阵,所以没注意到这个问题 } res += dis[t] * minflow; //到汇点的距离乘以流量,1个人对应1个流量 } return res; } int as(int x){return x<0 ? -x : x; } //绝对值被OJ编译器坑的不清,于是每次都自己重写了。 int hamt(pii a, pii b){ //返回哈密顿距离。 return as( a.first - b.first ) + as( a.second - b.second ); } vector<pii >per, ho; //保存人和房屋的坐标 方便建边 int main(){ while(scanf("%d %d%*c", &n, &m) != EOF && (n||m) ){ memset(fir, -1, sizeof(fir)); per.clear(); ho.clear(); s = 0, t = n*m +1, ecnt = 0; for(int i = 1; i <= n; ++i) gets(maz[i]+1); for(int i = 1; i <= n; ++i) // 因为i从1开始 for(int j = 1; j <= m; ++j){ if(maz[i][j] == 'H') { add( (i-1)*m+j, t, 1, 0); //房屋对汇点建边,容量1,花费0 ho.pb( make_pair(i, j) ); } if(maz[i][j] == 'm') { add(s, (i-1)*m +j , 1, 0); //源点对人建边,容量1,花费0 per.pb( make_pair(i,j) ); } } for(int i = 0; i <per.size(); ++i){ for(int j = 0; j < ho.size(); ++j){ //人对房屋建边,容量1,花费是哈密顿距离 add( (per[i].first-1)*m + per[i].second, (ho[j].first-1)*m +ho[j].second, 1, hamt(per[i], ho[j])); } } printf("%d\n",mincostEK(s, t, n*m+2) ); } }