洛谷P3980

连边方法(以下描述中,用二元组(f,w)表示容量为f费用为w的边):
对于每一天向后一天连边(inf-ai,0)
对于每一种志愿者选择i,s向t+1连边(inf,ci)
从超级源向第一天连边(inf,0)
从最后一天+1向超级汇连边(inf,0)
然后从超级源向超级汇跑费用流。
为什么这样跑会正确呢?可以发现,第一次网络流后所有天数边的容量会被填至max-a_i
(max为需求最大天的需求量),不会走带权边。然后因为有带权边存在,所以网络还可以扩容。因为保证一定存在可行解,所以容量一定可以扩成inf。
那么每条天数边都可以视为填满(因为天数边权值为0,一定优于带权边,会优先被填满,出现前面填带权边覆盖本条边情况除外)。然后对于每天,不通过天数边经过的流量总和一定至少为a_i
​换句话说,缺少的流量会从带权边流过,自动补齐inf。并且费用流算法会自动求出费用最小解,因此可以保证方案一定最优。*/
然后志愿者连向他能覆盖的下一天,这样如果选了这种志愿者,那么中间的天数都可以忽略了。
/参考http://blog.csdn.net/FZHvampire/article/details/50889187/
另一种解释:
我们假设每天都不需要志愿者,那么我们从源点向1号时间连inf的边,然后依次从上一个时间点王下一个时间点连inf的边。
这个时候我们每天需要志愿者了,那么我们在之前连的边的流量上减去相对应的每天需要的志愿者的人数。
对于m类的志愿者,假设他们属于的区间是[l,r],那么我们从l向r+1连(inf,cost)的边,cost为每个志愿者的费用。
这样从源点向最后一个时间点跑费用流,若果满流就说明符合条件,此时的费用就是最小的费用。
想想为什么要这样建图呢?
首先我们注意到一个问题,就是每天需要的人数只有上限没有下限,这样就导致我们没有办法去连边跑最大流。所以我们改成从inf减去这么多流量,就能解决这个问题了。
还有一个问题就是一类志愿者可以对一个区间都造成影响,而且不同的志愿者中间的区间是有重叠的。这样建图每次从有费用的边流的话就能把整个区间都覆盖住了,这样也弥补了这段区间的边中减小的流量。
这样我们就把这个问题很好的解决了。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<cstring>
using namespace std;
const int INF = (1 << 31) - 1;
struct edge {
    int to, cap, cost, rev;
};
vector<edge>G[2010];
int dis[2010], prevv[2010], preve[2010], n, m, flow = 0, cost = 0;
bool inque[2010];
void add(int from, int to, int cap, int cost)
{
    edge e;
    e.to = to; e.cap = cap; e.cost = cost; e.rev = G[to].size();
    G[from].push_back(e);
    e.to = from; e.cap = 0; e.cost = -cost; e.rev = G[from].size() - 1;//-cost!
    G[to].push_back(e);
}
bool Spfa(int s, int t)
{
    fill(dis, dis + 2000, 1 << 30); memset(inque, 0, sizeof(inque));
    queue<int>que;
    dis[s] = 0; inque[s] = true; que.push(s);
    while (!que.empty()) {
        int t = que.front(); que.pop(); inque[t] = false;
        for (int i = 0; i < G[t].size(); i++) {
            edge e = G[t][i];
            if (e.cap&&dis[e.to] > dis[t] + e.cost) {
                dis[e.to] = dis[t] + e.cost;
                prevv[e.to] = t;
                preve[e.to] = i;//一个边一个点不要混淆!
                if (!inque[e.to]) {
                    que.push(e.to);
                    inque[e.to] = true;
                }
            }
        }
    }
    if (dis[t] == 1 << 30)//如果已经无法增广,返回
        return false;
    int d = 1 << 30;
    for (int v = t; v != s; v = prevv[v])
        d = min(d, G[prevv[v]][preve[v]].cap);//此次可增广容量是全路径中容量最小的那个
    flow += d;
    cost += d * dis[t];//dis是路径中单位费用和
    for (int v = t; v != s; v = prevv[v]) {//更改容量
        edge &e = G[prevv[v]][preve[v]];
        e.cap -= d;
        G[v][e.rev].cap += d;//v或者e.to都可以
    }
    return true;
}
void mincostmaxflow(int s, int t)
{
    while (Spfa(s, t)&&flow<INF);
}
int main()
{
    int i, j;
    cin >> n >> m;
    add(0, 1, INF, 0); add(n + 1, 1500, INF, 0);
    for (i = 1; i <= n; i++) {
        scanf("%d", &j);
        add(i, i + 1, INF - j, 0);
    }
    for (i = 1; i <= m; i++) {
        int s, t, c;
        scanf("%d %d %d", &s, &t, &c);
        add(s, t + 1, INF, c);
    }
    mincostmaxflow(0, 1500);
    cout << cost << endl;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值