POJ - 2175 Evacuation Plan (网络流, SPFA 消去负环)

题目链接

题意:有 N 栋大楼,M 个避难所,每个大楼里有 Bi 个人,每个避难所有 Ci 的容量,人去避难所避难,

现在给你一个避难的计划,问你这个计划是不是最优的,如果不是最优的,那就找一个比这个优的方案,并输出来。

思路:问的是,这个方案是不是最优的,如果不是,输出一个更优的,并不是输出最优的。

我们要知道,最小费用最大流 等价于 跑完图之后,这个图没有 负环。

所以这个题就可以看看 残余网络上 有没有负环,如果没有,那么就是最优的解,如果有负环,在负环上增广一下,那就是更优的解了。

首先我们就要见残余网络的图。

 0  源点

1 ~ n 建筑物

n + 1 ~ n + m  避难所

n + m + 1 汇点

扫描二维码关注公众号,回复: 2587523 查看本文章

1、首先 人要去避难,, 建筑物 到  避难所 要建一条权值为距离的边

2、在给定的计划上,i 到 j 有人避难, 所以要建一条反向边, 权值为 负的。

3、在 j 这个避难所,是不是有避难的人,如果有,建一条从  汇点 到 j 的权值为 0 的边。

为什么?我的想法是, 人可以从 j 走到汇点,然后就要再一条 反向边。

如果 避难的人 没有超过当前避难所的容量,那就要建一条 从 避难所 到 汇点 的 权值 为 0 的边。

为什么? 因为,还有人可以从避难所走到汇点。

避难所就相当于汇点,不过避难所有多个,所以要一个超级汇点把避难所连接起来。

如果到了避难所,就相当于到了汇点,避难所有人,相当于到了汇点,然后建一条反向边,

如果避难所的人等于避难所的容量,相当于都到了汇点,就不会有人 还能从避难所到汇点,这种情况就不能建一条从避难所

到汇点的边,否则就建一条从避难所到汇点的边。

之后就是,SPFA  判断负环,如果有一个点的入度 > 点的总数,说明有负环,那就退出来,

但是这个点不一定在负环中,所以我们就要找到负环,

从当前点找前驱,如果有一个点的出现过了,说明这个点在负环中。

然后就是在负环中增广, 改变流量,找最优值。

这个就是看负环的流向了。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using  namespace std;
#define mem(x,v) memset(x,v,sizeof(x))
const int INF = 0x3f3f3f3f;
const int M = 1000000;
const int N = 500;
struct edge{
    int u,v,w,cost;
}f[M];
struct node{
    int x,y,z;
}bu[250],sh[250];
int cnt = -1,Next[M],pre[N],dis[N],head[N],len[250][250]; //len 表示距离
int Map[250][250],c[250],du[250]; // 存原图,以及答案。
bool vis[N];
int n,m,s,t,sum = 0;
void _Add(int u, int v, int cost){
    cnt++;
    Next[cnt] = head[u];
    head[u] = cnt;
    f[cnt].u = u;
    f[cnt].v = v;
    f[cnt].cost = cost;
    return;
}
void init() {
    int z;
    scanf("%d%d", &n, &m);
    s = 0; t = n + m + 1;
    mem(head,-1);
    for (int i = 1; i <= n; i++)
        scanf("%d%d%d", &bu[i].x, &bu[i].y, &bu[i].z);
    for (int i = 1; i <= m; i++) 
        scanf("%d%d%d", &sh[i].x, &sh[i].y, &sh[i].z);

    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++){
            len[i][j] = abs(bu[i].x - sh[j].x) + abs(bu[i].y - sh[j].y) + 1;//求建筑和避难所的距离。
            _Add(i, n + j, len[i][j]);//然后就要建边,残余网络。
        }

    for (int i = 1; i <= n; i++){
        for (int j = 1; j <= m; j++){
            scanf("%d",&z);
            c[j] += z;
            Map[i][j] = z;
            if (z > 0) _Add(n + j, i, -len[i][j]);//从 i 建筑到 j 避难所,要建一条反向边,权值为负的。
            }
        }
    for (int i = 1; i <= m; i++){
        if (c[i] > 0) _Add(t,n+i,0); //避难所有人,就要建一条从汇点到避难所的反向边,权值为0.因为可以从避难所走到汇点。
        if (c[i] < sh[i].z) _Add(n + i, t, 0);//避难所的人没有超过最大限度,所以要建一条边到 汇点,权值为 0;
    }    
    return;
}

int spfa(){
    mem(pre,-1);//前驱
    mem(dis,INF);//求距离,
    mem(vis,0); 
    mem(du,0); //入度;
    dis[t] = 0;
    queue<int>q;
    while (!q.empty()) q.pop();
    q.push(t);
    vis[t] = 1;
    du[t] = 1;
    while(!q.empty()){
        int u = q.front(); q.pop();
        vis[u] = 0;
        for (int i = head[u];i != -1; i = Next[i]){
            int v = f[i].v;
            if (dis[v] > dis[u] + f[i].cost){
                dis[v] = dis[u] + f[i].cost;
                pre[v] = u; 
                if (du[v] > n) return v;
                if (vis[v] == 0){
                    du[v]++;
                    if (du[v] > t) return v;//一开始,t 写成了 n; 照样过了。判断负环要 > t;
                    vis[v] = 1;
                    q.push(v);
                }
            }
        }
    }
    return -1;
}

int main() {
    init();
    int ans = spfa();
   if (ans == -1) printf("OPTIMAL\n"); else {
    printf("SUBOPTIMAL\n");
    int now = ans;
    int last;
    mem(vis,0);
    while(!vis[now]){//找负环,spfa 出来的点不一定在负环上,所以要找负环上的点。
        vis[now] = 1;
        now = pre[now];
    } //同一个点出现两次,就说明这个点在负环上。
    ans = now;
    last = pre[now];
    do{ //在负环上增广,看负环流向,从建筑物流到避难所 ++, 从避难所流到建筑物 --。
        //printf("%d %d\n",last,now);
        if (last > 0 && last <= n && now > n) Map[last][now-n]++; else
        if (last > n && now <= n && now > 0) Map[now][last-n]--;
        now = last;
        last = pre[now];
    }while(now != ans);
    for (int i = 1; i <= n; i++){
        for (int j = 1; j <= m; j++)
            printf("%d%c",Map[i][j], j == m ? '\n' :' ');
    }
   }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/kidsummer/article/details/81287759