首页 > 代码库 > POJ 2175 Evacuation Plan (费用流,负环,消圈法,SPFA)

POJ 2175 Evacuation Plan (费用流,负环,消圈法,SPFA)

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

Evacuation Plan
Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 3256 Accepted: 855 Special Judge

Description

The City has a number of municipal buildings and a number of fallout shelters that were build specially to hide municipal workers in case of a nuclear war. Each fallout shelter has a limited capacity in terms of a number of people it can accommodate, and there‘s almost no excess capacity in The City‘s fallout shelters. Ideally, all workers from a given municipal building shall run to the nearest fallout shelter. However, this will lead to overcrowding of some fallout shelters, while others will be half-empty at the same time. 

To address this problem, The City Council has developed a special evacuation plan. Instead of assigning every worker to a fallout shelter individually (which will be a huge amount of information to keep), they allocated fallout shelters to municipal buildings, listing the number of workers from every building that shall use a given fallout shelter, and left the task of individual assignments to the buildings‘ management. The plan takes into account a number of workers in every building - all of them are assigned to fallout shelters, and a limited capacity of each fallout shelter - every fallout shelter is assigned to no more workers then it can accommodate, though some fallout shelters may be not used completely. 

The City Council claims that their evacuation plan is optimal, in the sense that it minimizes the total time to reach fallout shelters for all workers in The City, which is the sum for all workers of the time to go from the worker‘s municipal building to the fallout shelter assigned to this worker. 

The City Mayor, well known for his constant confrontation with The City Council, does not buy their claim and hires you as an independent consultant to verify the evacuation plan. Your task is to either ensure that the evacuation plan is indeed optimal, or to prove otherwise by presenting another evacuation plan with the smaller total time to reach fallout shelters, thus clearly exposing The City Council‘s incompetence. 

During initial requirements gathering phase of your project, you have found that The City is represented by a rectangular grid. The location of municipal buildings and fallout shelters is specified by two integer numbers and the time to go between municipal building at the location (Xi, Yi) and the fallout shelter at the location (Pj, Qj) is Di,j = |Xi - Pj| + |Yi - Qj| + 1 minutes. 

Input

The input consists of The City description and the evacuation plan description. The first line of the input file consists of two numbers N and M separated by a space. N (1 ≤ N ≤ 100) is a number of municipal buildings in The City (all municipal buildings are numbered from 1 to N). M (1 ≤ M ≤ 100) is a number of fallout shelters in The City (all fallout shelters are numbered from 1 to M). 

The following N lines describe municipal buildings. Each line contains there integer numbers Xi, Yi, and Bi separated by spaces, where Xi, Yi (-1000 ≤ Xi, Yi ≤ 1000) are the coordinates of the building, and Bi (1 ≤ Bi ≤ 1000) is the number of workers in this building. 

The description of municipal buildings is followed by M lines that describe fallout shelters. Each line contains three integer numbers Pj, Qj, and Cj separated by spaces, where Pi, Qi (-1000 ≤ Pj, Qj ≤ 1000) are the coordinates of the fallout shelter, and Cj (1 ≤ Cj ≤ 1000) is the capacity of this shelter. 

The description of The City Council‘s evacuation plan follows on the next N lines. Each line represents an evacuation plan for a single building (in the order they are given in The City description). The evacuation plan of ith municipal building consists of M integer numbers Ei,j separated by spaces. Ei,j (0 ≤ Ei,j ≤ 1000) is a number of workers that shall evacuate from the ith municipal building to the jth fallout shelter. 

The plan in the input file is guaranteed to be valid. Namely, it calls for an evacuation of the exact number of workers that are actually working in any given municipal building according to The City description and does not exceed the capacity of any given fallout shelter. 

Output

If The City Council‘s plan is optimal, then write to the output the single word OPTIMAL. Otherwise, write the word SUBOPTIMAL on the first line, followed by N lines that describe your plan in the same format as in the input file. Your plan need not be optimal itself, but must be valid and better than The City Council‘s one.

Sample Input

3 4
-3 3 5
-2 -2 6
2 2 5
-1 1 3
1 1 4
-2 -2 7
0 -1 3
3 1 1 0
0 0 6 0
0 3 0 2

Sample Output

SUBOPTIMAL
3 0 1 1
0 0 6 0
0 4 0 1

Source

Northeastern Europe 2002


题意:

有N幢楼,M个避难所,给出坐标,楼中人数及避难所容量,每人从楼到避难所的花费是曼哈顿距离+1。现给出一种避难方案,判断是否是最优的(所有人的花费之和最小),如果不是,给出一种更优的方案。

分析:

很容易想到是费用流。源点到楼连边,容量为楼中人数,单位流量费用为0;避难所到汇点连边,容量为避难所容量,单位流量费用为0;楼到避难所连边,容量为无穷大,单位流量费用为曼哈顿距离+1。在此图中跑一遍最小费用最大流,如果最小费用比给出的方案费用小,则对应的流的方案更优(且是最优的)。

上述思路虽然没错,但很不幸,效率很低,我毫无疑问地获得了TLE,那么有没有更好的算法呢?

按最小费用最大流跑出的流对应最优的方案,那么已经给出的方案显然对应某个最大流,题目要求判断给出的方案是否是最优的<=>该流的费用最小。如果某流f是最小费用流<=>残留网络中没有负圈,如果有负圈,则沿着该负圈增广,就能得到相同流量下费用更小的流。判断负圈只要用SPFA,在实际测试中,用栈比队列判断负圈效率更高。值得注意的是,如果某个点进栈/队N次(假设一共N个点),那么图中存在负环,但是这个点却未必在负环中(是不是很惊悚?),只能说这个点被负环更新过,即负环在这个点之前,所以我们只要从这个点往前找到负环,然后在负环中增广,即可获得费用更小的流。


#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<algorithm>
#include<ctime>
#include<cctype>
#include<cmath>
#include<string>
#include<cstring>
#include<stack>
#include<queue>
#include<list>
#include<vector>
#include<map>
#include<set>
#define sqr(x) ((x)*(x))
#define LL long long
#define itn int
#define INF 0x3f3f3f3f
#define PI 3.1415926535897932384626
#define eps 1e-10
#define maxm (65536)
#define maxn (202)

using namespace std;

struct __point
{
    int x,y,p;
}b[101],sh[101];
int n,m;

int fir[maxn];
int u[maxm],v[maxm],cap[maxm],flow[maxm],cost[maxm],nex[maxm];
int e_max;
int q[maxn],d[maxn],prev[maxn],cnt[maxn];
bool inq[maxn];

int G[101][101];
int shelter[101];
bool vis[maxn];

void add_edge(int _u,int _v,int _cap,int _cost,int _f)
{
    int e;
    e=e_max++;
    u[e]=_u;v[e]=_v;cap[e]=_cap;cost[e]=_cost;flow[e]=_f;
    nex[e]=fir[u[e]];fir[u[e]]=e;
    e=e_max++;
    u[e]=_v;v[e]=_u;cap[e]=0;cost[e]=-_cost;flow[e]=-_f;
    nex[e]=fir[u[e]];fir[u[e]]=e;
}

int negative_loop(int s,int t)
{
    int f,r,top=-1;
    f=0;r=-1;

    memset(cnt,0,sizeof cnt);
    memset(d,0,sizeof d);
    memset(inq,1,sizeof inq);//inq是bool类型的数组,每个元素占1字节,所以可以这样
    memset(prev,-1,sizeof prev);
    for (int i=s;i<=t;i++)
        q[++r]=i;

    while (f<=r)
    {
        int x=q[r--];//栈式写法,r--改成f++就是队列了,队列要将数组扩大。
        inq[x]=false;
        for (int e=fir[x];~e;e=nex[e])
        {
            if (cap[e]>flow[e] && d[v[e]]>d[u[e]]+cost[e])
            {
                d[v[e]]=d[u[e]]+cost[e];
                prev[v[e]]=e;
                if (!inq[v[e]])
                {
                    q[++r]=v[e];
                    inq[v[e]]=true;
                    cnt[v[e]]++;
                    if (cnt[v[e]]>t-s+1)    return v[e];
                    //如过不想判断边界,就把这个值调大些,因为如果没有负环就不会进队/栈那么多次,如果有负环,就会无限循环下去
                }
            }
        }
    }

    return -1;
}


inline int __cost(const __point &p1,const __point &p2)
{
    return abs(p1.x-p2.x)+abs(p1.y-p2.y)+1;
}

int main()
{
    #ifndef ONLINE_JUDGE
        freopen("/home/fcbruce/文档/code/t","r",stdin);
    #endif // ONLINE_JUDGE

    int p;
    int s,t;
    scanf("%d%d",&n,&m);
    s=0;t=n+m+1;
    e_max=0;
    memset(fir,-1,sizeof fir);

    for (int i=0;i<n;i++)
    {
        scanf("%d%d%d",&b[i].x,&b[i].y,&b[i].p);
        add_edge(s,i+1,b[i].p,0,b[i].p);
    }

    for (int i=0;i<m;i++)
    {
        scanf("%d%d%d",&sh[i].x,&sh[i].y,&sh[i].p);
    }

    memset(shelter,0,sizeof shelter);
    for (int i=0;i<n;i++)
    {
        for (int j=0;j<m;j++)
        {
            scanf("%d",&p);
            shelter[j]+=p;
            add_edge(i+1,n+j+1,INF,__cost(b[i],sh[j]),p);
        }
    }

    for (int i=0;i<m;i++)
    {
        add_edge(i+1+n,t,sh[i].p,0,shelter[i]);
    }

    int k=negative_loop(s,t);

    if (k!=-1)
    {
        puts("SUBOPTIMAL");
        memset(vis,0,sizeof vis);

        for (int e=prev[k];!vis[v[e]];e=prev[u[e]])//往前找负环
        {
            vis[v[e]]=true;
            k=v[e];
        }

        for (int e=prev[k];;e=prev[u[e]])//在负环中增广
        {
            flow[e]++;//只要找一个更优的解,+1就行
            flow[e^1]--;
            if (u[e]==k)break;
        }

        for (int e=0;e<e_max;e++)
        {
            if (u[e]>0 && u[e]<=n && v[e]>n && v[e]<=n+m)
                G[u[e]-1][v[e]-n-1]=flow[e];
        }

        for (int i=0;i<n;i++)
        {
            for (int j=0;j<m;j++)
            {
                if (j) putchar(' ');
                printf("%d",G[i][j]);
            }
            putchar('\n');
        }
    }
    else
        puts("OPTIMAL");


    return 0;
}