POJ2175_Evacuation Plan_消负圈的“更小”费用流

题意

POJ2175题目翻译

思路

要确定两类物体之间的对应关系,并希望使总花费最小的问题称为指派问题,一般可以用最小费用流来求解

添加一个源点一个汇点。
从源点向每幢大楼连一条容量为大楼内人数,费用为0的边。
从每个防空洞向汇点连一条容量为防空洞容量,费用为0的边。
在大楼和防空洞之间连边,容量为无穷,费用为两者之间的距离。
若求最佳方案,直接在这样建好的图上跑最小费用流就好了。

这个题如果直接跑最小费用流,再拿结果和原始方案对比的话,答案没错,问题是TLE。
注意题目要求的不是“最优”,而是”更优“,即比原始方案强就行了。
这里写图片描述

题目链接

http://poj.org/problem?id=2175

AC代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstring>

using namespace std;

const int maxn = 100 + 10;
const int maxv = 220;
const int  inf = 0x3f3f3f3f;

int N, M;
int X[maxn], Y[maxn], B[maxn];                      //建筑的横、纵坐标,人数
int P[maxn], Q[maxn], C[maxn];                      //防空洞的横、纵坐标,容量
int E[maxn][maxn];                                  //原始方案,从i建筑到j洞中的人数

int g[maxv][maxv];                                  //花费矩阵(这里的花费等价于距离)
int prev[maxv][maxv];                               //从i到j的最短路径中,j的前驱
bool usd[maxv];                                     //找环时用到的标记数组
                                        //0~N表示建筑,N+1~N+M表示防空洞,N+M+1表示汇点
int Abs(int x)
{
    return x > 0 ? x : -x;
}

int main()
{
    cin >> N >> M;

    for(int i= 0; i< N; i++)
        cin >> X[i] >> Y[i] >> B[i];

    for(int i= 0; i< M; i++)
        cin >> P[i] >> Q[i] >> C[i];

    for(int i= 0; i< N; i++)
        for(int j= 0; j< M; j++)
            cin >> E[i][j];

    int V = N + M + 1;                              //点的总数(添加一个超级汇点)

    memset(g, inf, sizeof g);                       //初始化距离为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++)            //从i到j最短路径中,j的前继初始化为i,即从i直接到j
        for(int j= 0; j< V; j++)
        prev[i][j] = i;

    for(int k= 0; k< V; k++)                                //floyd算法找负环
        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)               //用floyd算法找到负环的标志
                    {
                        memset(usd, false, sizeof usd);         //初始化标记数组

                        for(int v = i; !usd[v]; v = prev[i][v]) //沿着负环这条路径往前找
                        {
                            usd[v] = true;                          //访问标记

                            if(v != N + M && prev[i][v] != N + M)   //对于汇点不进行操作
                            {
                                if(v >= N) E[prev[i][v]][v - N] ++; //表示防空洞的点
                                else E[v][prev[i][v] - N] --;       //表示建筑的点
                            }       //这里只改变1,因为题意是求一个更优方案,不需要最优
                        }

                        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 0;
                    }
                }
            }

    printf("OPTIMAL\n");                                        //原始方案为最优

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值