hdu 3667 Transportation(最小费用流+拆边)

46 篇文章 0 订阅

Transportation

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 1908    Accepted Submission(s): 789


Problem Description
There are N cities, and M directed roads connecting them. Now you want to transport K units of goods from city 1 to city N. There are many robbers on the road, so you must be very careful. The more goods you carry, the more dangerous it is. To be more specific, for each road i, there is a coefficient a i. If you want to carry x units of goods along this road, you should pay a i * x 2 dollars to hire guards to protect your goods. And what’s worse, for each road i, there is an upper bound C i, which means that you cannot transport more than C i units of goods along this road. Please note you can only carry integral unit of goods along each road.
You should find out the minimum cost to transport all the goods safely.
 

Input
There are several test cases. The first line of each case contains three integers, N, M and K. (1 <= N <= 100, 1 <= M <= 5000, 0 <= K <= 100). Then M lines followed, each contains four integers (u i, v i, a i, C i), indicating there is a directed road from city u i to v i, whose coefficient is a i and upper bound is C i. (1 <= u i, v i <= N, 0 < a i <= 100, C i <= 5)
 

Output
Output one line for each test case, indicating the minimum cost. If it is impossible to transport all the K units of goods, output -1.

 

Sample Input
   
   
2 1 2 1 2 1 2 2 1 2 1 2 1 1 2 2 2 1 2 1 2 1 2 2 2
 

Sample Output
   
   
4 -1 3



【题意】:有n个城市,m条有方向的路。你想从城市1运输k个单位的货物到城市n,但每条路上都有很多强盗,因此通过每条路你都必须要小心的保护好你的货物。对于每条路i,给出一个系数ai,如果你想在这条路上运输x个单位的货物,就要花费ai*x*x的费用以便去保护你的货物;再给出一个ci,表示在这条路上最多运输ci个单位的货物。如果能运输完所有货物的话,输出最小的费用;否则,输出-1。

【题解】:这题好明显是一个最小费用流的模型。但问题在于,通过每条路的费用不是跟流成正比,而是跟流的平方成正比。
              观察数据,发现每条路的容量ci <= 5,是不是觉得好奇怪,为什么边的容量ci <= 5。其实,题目这是在暗示我们要拆边。
              举一个例子,有边<u,v>,c为5,a为1。
              我们在<u,v>上分别运输x个单位货物:
                            x                  1      2      3      4      5
                          cost                1      4      9     16     25
                 cost[x] - cost[x-1]            3      5      7      9        观察这一行,显然这是一个等差数列。   n^2 = 1 + 3 + 5 + … +(2n - 1). [关键:拆边的依据]
              观察到这个规律:我们就可以对所有边<u,v> ,ai,ci这样处理,把当前边拆分成 ci 条<u,v>边,每条边的费用是 (2*i - 1)* ai  [ 1 <= i <= ci ],容量都是1。为了下面方便,我们叫这样的ci条边为一组边。
              拆完之后,图上每条边的容量都变成1,而且边上的费用跟流成正比。
              对于每组边的选择,我们肯定是从编号小的先选起,因为编号小的费用小。假如对于某组边,我们选择了其中x条,那就意味着我们在原<u,v>边上运输了x个单位的货物,那么费用应该是ai*x*x。而前x条边的费用总和 = [ 1 + 3 + … + (2 * x - 1)] * ai = x*x*ai。这就证明了我们拆边的正确性。
              又因为题目问最终能否运输完k个单位的货物,对于这个问题我们有两种处理办法:
              1.加入一个超级源点s,连边s->1,容量为k,费用为0.表示最多运k个单位货物。
              2.限制费用流运行的条件,平时费用流限制条件是能否找到增广路,我们只需再加入一个条件,ans < k(ans表示当前的费用流的流量)。
              最后判断一下ans是否等于k就可以了。

AC代码:
#include <iostream>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <queue>
#include <ctime>
#include <algorithm>
#define ll __int64

using namespace std;

const int INF = 1000000000;
const int maxn = 200;

struct Edge{
    int u, v, cost, cap, flow, next;
}et[maxn * maxn];
int low[maxn], pre[maxn], dis[maxn], eh[maxn];
bool vis[maxn];
int s, t, num, n, m, k, anscost, ans;
void init(){
    memset(eh, -1, sizeof(eh));
    num = 0;
}
void add(int u, int v, int cost, int cap, int flow){
    Edge e ={u, v, cost, cap, flow, eh[u]};
    et[num] = e;
    eh[u] = num++;
}
void addedge(int u, int v, int cost, int cap){
    add(u, v, cost, cap, 0);
    add(v, u, -cost, 0, 0);
}
bool spfa(){
    queue<int> Q;
    memset(low, 0, sizeof(low));
    memset(vis, false, sizeof(vis));
    memset(pre, -1, sizeof(pre));
    fill(&dis[0], &dis[maxn], INF);
    dis[s] = 0, low[s] = INF, vis[s] = true;
    Q.push(s);
    while(!Q.empty())
    {
        int u = Q.front();
        Q.pop();
        vis[u] = false;
        for(int i = eh[u]; i != -1; i = et[i].next)
        {
            int v = et[i].v, cost = et[i].cost, cap = et[i].cap, flow = et[i].flow;
            if(cap - flow && dis[v] > dis[u] + cost)
            {
                dis[v] = dis[u] + cost;
                pre[v] = i;
                low[v] = min(low[u], cap - flow);
                if(!vis[v])
                {
                    Q.push(v);
                    vis[v] = true;
                }
            }
        }
    }
    return dis[t] != INF;
}
void costflow(){
    anscost = ans = 0;
    while(spfa() && ans < k)
    {
        int x = pre[t];
        anscost += low[t] * dis[t];
        ans += low[t];
        while(x != -1)
        {
            et[x].flow += low[t];
            et[x^1].flow -= low[t];
            x = pre[et[x].u];
        }
    }
}
int main()
{
    int u, v, a, c;
    while(~scanf("%d%d%d", &n, &m, &k))
    {
        init();
        s = 1, t = n;
        addedge(s, 1, 0, k);
        while(m--)
        {
            scanf("%d%d%d%d", &u, &v, &a, &c);
            for(int i = 1; i <= c; i++) addedge(u, v, (2 * i - 1) * a, 1);
        }
        costflow();
        if(ans < k) printf("-1\n");
        else printf("%d\n", anscost);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值