poj 2195 (最小费用最大流,入门模版)

题目链接: http://poj.org/problem?id=2195

最小费用最大流: 即在有向图中每条边除了有容量外, 还有花费。 最大流的路径条数可能不止一条,需要求的就是 花费最小的那一条。 可以将花费视为 权值路径, 所以每一次增广 所用的花费就是  源点到汇点的最短距离 * 每次增广的 流量大小。 以此求得的总费用, 就是达到最大流的最小费用。

题意: 给定一张 n*m 大小的地图, 图中的m 代表人 ,H代表房子(人的数量 == 房子的数量), 每走一步花费为1 , 问这些人都各自走到一间房子 需要的最小花费.

思路: 设人数为 cnt1, 房子数为 cnt2, 超级源点为 0, 超级汇点则为 cnt1 + cnt2 + 1; 连接源点到所有的人(容量为1, 花费为0), 在连接所有的房子到汇点(容量为1, 花费为0), 最后连接每个人到每个房子(容量为1, 花费为 两个点之间的曼哈顿距离)。  题目所求 即为 此形成的图的 最小费用最大流0

AC代码:

#include<cstdio>
#include<queue>
#include<cstring>
#include<cmath>
#include<vector>
using namespace std;

const int maxn = 400;
const int inf = 0x3f3f3f3f;
char mp[maxn][maxn];
struct Edge{
    int u,v,cap,cost;
    Edge(int u,int v,int cap,int cost):u(u),v(v),cap(cap),cost(cost){}
};
int pre[maxn];          ///前驱数组, 每一次记录 访问点i 之前的那一条边的下标
int minFlow[maxn];      ///
vector<Edge> edges;     ///存放所有边
vector<int> G[maxn];    ///邻接表表头
int dis[maxn];          ///SPFA 的dis (一样的意义)
int vis[maxn];          ///SPFA 的vis (一样的意义)

void init(){
    for(int i = 0;i < maxn;i ++)
        G[i].clear();
    edges.clear();
}

void add(int u,int v,int cap,int cost){
    edges.push_back(Edge(u,v,cap,cost));
    edges.push_back(Edge(v,u,0,-cost));
    int m = edges.size();
    G[u].push_back(m-2);                 ///每次都将正向边和反向边存一起。边 1 2 互为反向边, 3 4 互为反向边
    G[v].push_back(m-1);                 ///所以, 边i的反向就是 i^1(^ 异或) 
}

bool SPFA(int s,int t,int &cost){
    for(int i = 0;i < maxn;i ++) dis[i] = inf;
    memset(vis,0,sizeof(vis));
    dis[s] = 0; vis[s] = 1; pre[s] = 0; minFlow[s] = inf;
    queue<int> Q; Q.push(s);
    while(!Q.empty()){
        int now = Q.front();
        Q.pop();
        vis[now] = 0;
        for(int i = 0;i < G[now].size();i ++){
            Edge &e = edges[G[now][i]];
            if(e.cap > 0 && dis[e.v] > dis[now] + e.cost){      ///如果容量 > 0 (也就是还可增广)
                dis[e.v] = dis[now] + e.cost;
                pre[e.v] = G[now][i];
                minFlow[e.v] = min(minFlow[now],e.cap);         ///计算整个增广路径 中 最小的可增广的 流量
                if(!vis[e.v]){
                    vis[e.v] = 1;
                    Q.push(e.v);
                }
            }
        }
    }
    if(dis[t] == inf) return 0;         ///已经无法到达汇点
    cost += dis[t] * minFlow[t];        ///每一次增广的花费就是  每次增广的流量大小 * 最短路径
    int u = t;
    while(u != s){
        edges[pre[u]].cap -= minFlow[t];    ///正向 减少    ... 式1
        edges[pre[u]^1].cap += minFlow[t];  ///反向 增加    ... 式2
        u = edges[pre[u]].u;                ///通过前驱数组, 遍历增广路径, 做 式1, 式2
    }
    return 1;
}

int min_cost_maxflow(int s,int t){
    int cost = 0;
    while(SPFA(s,t,cost));
    return cost;
}

int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m)){
        if(!n && !m) break;
        init();
        for(int i = 0;i < n;i ++)
            scanf("%s",mp[i]);
        int cnt1 = 0,cnt2 = 0;
        struct xx{ int x,y; }s1[maxn], s2[maxn];
        for(int i = 0;i < n;i ++){
            for(int j = 0;j < m;j ++){
                if(mp[i][j] == 'm')
                    s1[cnt1].x = i, s1[cnt1].y = j, cnt1 ++;
                else if(mp[i][j] == 'H')
                    s2[cnt2].x = i, s2[cnt2].y = j, cnt2 ++;
            }
        }
        for(int i = 0;i < cnt1;i ++)
            add(0,i + 1,1,0);           ///相当于给人编号, 编号为 1 -- cnt1, 并连接
        for(int i = 0;i < cnt2;i ++)
            add(cnt1 + i + 1,cnt1 + cnt2 + 1,1,0);
        for(int i = 0;i < cnt1;i ++)   ///相当于给房子编号, 为 cnt1 + 1 到 cn1 + cnt2, 并连接
            for(int j = 0;j < cnt2;j ++)
                add(i + 1,cnt1 + j + 1,1,abs(s1[i].x - s2[j].x) + abs(s1[i].y - s2[j].y));      ///连接人与房子
        printf("%d\n",min_cost_maxflow(0,cnt1 + cnt2 + 1));
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/no_O_ac/article/details/81332780
今日推荐