Evacuation Plan

题目描述:
有n个建筑物,m个避难所,告诉你每个建筑物的坐标和拥有的人。告诉你每个避难所的坐标和容量。建筑物i和避难所j的距离为|xi-xj| + |yi-yj| + 1。现在要求把建筑物的人都疏散到避难所。给你一个方案,n行m列,i行j列表示建筑物i有x[i][j]的人去j避难所。
问你这个方案是不是最优的,不是的话,输出一个比给定方案好的方案即可(SPJ)。
解题报告:
         如果直接求最小费用流求出最优值输出,算法正确,但是会TLE,注意到题目不一定要求输出最优,好一点就可以。
         “可行流x为最小费用流的充要条件是残量网络中不存在负费用增广圈”
         按照这个条件,我们建立残量网络即可,由于要判断负圈,所以只要剩余容量大于0,就连接边即可,走几遍都无所谓。
         为了简化,可以不需要原来的源点(因为需要源点满流,所以不会沿着建筑物点走回源点当然建立了也无所谓)。
         省下的就是残量网络建图:
         所有的建筑物i和避难所j,连接ij,边权为正的距离。
         如果原方案i到j有人,连接ji,边权为负的距离。
         如果j避难所的人数大于0,连接汇点和j,边权0.
         如果j避难所没有满,连接j和汇点,边权0.
个人理解如下:
 1 : 因为汇点包含在负圈里, 所以连接汇点和j。
 2 : 未满,所以可以增广调整,而增广则需与汇点相连。
         这样,在残量网络中,容量大于0的边就都建立出来了。
         从汇点出发,找负圈,如果找到了,按照负圈的边,依次更改方案即可。
         比如负圈中有建筑物到i到避难所j的点,x[i][j]++
         如果有避难所j到建筑物i的点,x[i][j]—
         输出方案即可。
代码如下:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
#define maxn 122
#define maxv 333
#define INF 1e9
int N,M;
int V;
int X[maxn],Y[maxn],B[maxn];
int P[maxn],Q[maxn],C[maxn];
int E[maxn][maxn];
int g[maxv][maxv];
int prev[maxv][maxv];
bool used[maxv];

void read()
{
    scanf("%d %d",&N,&M);
    for(int i = 0 ; i < N ; ++i)
        scanf("%d %d %d",&X[i],&Y[i],&B[i]);
    for(int i = 0 ; i < M ; ++i)
        scanf("%d %d %d",&P[i],&Q[i],&C[i]);
    for(int i = 0 ; i < N ; ++i)
        for(int j = 0 ; j < M ; ++j)
            scanf("%d",&E[i][j]);
}

void solve()
{
    V = N + M + 1;
    for(int i = 0 ; i < V ; ++i){fill(g[i] , g[i] + V , INF);}
    for(int j = 0 ; j < M ; ++j){
        int sum = 0 ;
        for(int i = 0 ; i < N ; ++i){
           int c = abs(X[i] - P[j]) + abs(Y[i] - Q[j]) + 1;
           g[i][N+j] = c;
           if( E[i][j] > 0 )g[N+j][i] = -c;//因为只需要增流一次
           sum += E[i][j];
        }
        if( sum > 0 )g[N+M][N+j] = 0;
        if( sum < C[j] )g[N+j][N+M] = 0;
    }

    for(int i = 0 ; i < V ; ++i){
        for(int j = 0 ; j < V ; ++j){
            prev[i][j] = i;
        }
    }

    for(int k = 0 ; k < V ; ++k){
        for(int i = 0 ; i < V ; ++i){
            for(int j = 0 ; j < V ; ++j){
                if(g[i][j] > g[i][k] + g[k][j]){
                    g[i][j] = g[i][k] + g[k][j];
                    prev[i][j] = prev[k][j];//类似链
                    if( i == j && g[i][i] < 0 ){//形成环
                        fill(used,used + V,false);
                        for(int v = i ; !used[v]; v = prev[i][v]){
                            used[v] = true;
                            if( v >= N )
                                E[prev[i][v]][v-N]++;
                            else E[v][prev[i][v] - N]--;
                        }
                        printf("SUBOPTIMAL\n");
                        for(int x = 0 ; x < N ; ++x){
                            for(int y = 0 ; y < M ; ++y){
                                printf("%d%c",E[x][y],y+1 == M ?'\n':' ');
                            }
                        }
                        return ;
                    }
                }
            }
        }
    }
    printf("OPTIMAL\n");
}
int main()
{
    read();
    solve();
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值