[费用流] POJ 2195 Going Home

题目链接: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) );
    }
}


你可能感兴趣的:(ACM,poj,费用流)