【NOIP 费用流】JZOJ_3029 观光公交

题目

题目太长请自行阅读。
风景迷人的小城 Y Y Y市,拥有 n n n个美丽的景点。由于慕名而来的游客越来越多, Y Y Y市特意安排了一辆观光公交车,为游客提供更便捷的交通服务。观光公交车在第 0 0 0分钟出现在 1 1 1号景点,随后依次前往 2 、 3 、 4 … … n 2、3、4……n 234n号景点。从第 i i i号景点开到第 i + 1 i+1 i+1号景点需要 D i D_i Di 分钟。任意时刻,公交车只能往前开,或在景点处等待。
设共有 m m m个游客,每位游客需要乘车 1 1 1次从一个景点到达另一个景点,第 i i i位游客在 T i T_i Ti 分钟来到景点 A i A_i Ai,希望乘车前往景点 B i ( A i &lt; B i ) B_i(Ai&lt;Bi) Bi(Ai<Bi)。为了使所有乘客都能顺利到达目的地,公交车在每站都必须等待需要从该景点出发的所有乘客都上车后才能出发开往下一景点。假设乘客上下车不需要时间。
一个乘客的旅行时间,等于他到达目的地的时刻减去他来到出发地的时刻。因为只有一辆观光车,有时候还要停下来等其他乘客,乘客们纷纷抱怨旅行时间太长了。于是聪明的司机 Z Z ZZ ZZ 给公交车安装了 k k k个氮气加速器,每使用一个加速器,可以使其中一个 D i D_i Di 1 1 1。对于同一个 D i D_i Di可以重复使用加速器,但是必须保证使用后 D i Di Di大于等于 0 0 0
那么 Z Z ZZ ZZ该如何安排使用加速器,才能使所有乘客的旅行时间总和最小?

思路

先求出不加速的答案,再用费用流求出加速可以减去的时间,最后就可以得出答案了。
首先我们要求出几个东西 l a s t last last d o w n down down g e t get get
其中 l a s t i last_i lasti表示做第 i i i站最晚乘客的到达时间。
d o w n i down_i downi表示第 i i i站下车乘客的数量。
g e t i get_i geti表示第 i i i站车到达的时间。
那么不加速的答案就为 ∑ i = 1 m g e t p i . b − p i . t \sum_{i=1}^{m}get_{p_i.b}-p_i.t i=1mgetpi.bpi.t
考虑费用流,我们这样构图:
s → s ′ s \rightarrow s&#x27; ss,容量为 k k k,费用为 0 0 0,限制了加速器的个数。
对于每一个点,我们把它们拆成 i i i i ′ i&#x27; i
s ′ → i ′ s&#x27; \rightarrow i&#x27; si,容量为 d i d_i di,费用为 0 0 0,表示最多用 d i d_i di个氮气。
i → i ′ i \rightarrow i&#x27; ii,容量为 m a x ( g e t i − l a s t i , 0 ) max(get_i-last_i,0) max(getilasti,0),费用为 0 0 0,代表这个站可以使用这么多个氮气加速使得后面能受到影响,因为过早地到达也要等待巴士到来。
i ′ → i + 1 i&#x27; \rightarrow i+1 ii+1,容量为 i n f inf inf,费用为 d o w n i + 1 down_{i+1} downi+1,表示接受氮气可以使经过这条路的人都减少 1 1 1个时间。
i + 1 → t i+1\rightarrow t i+1t,容量为 i n f inf inf,费用为 0 0 0,把流量流到汇点。

代码

#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>

struct people{
	int t, a, b;
}p[100001];
struct node{
    int x, y, next, cost, flow;
}e[8001];
int n, m, k, ans, tot = 1;
int s, s1, t;
int d[1001], last[1001], get[1001], down[1001], head[2004], pre[2004], dis[2004], v[2004];

const int inf = 2147483647;

void add(int x, int y, int flow, int cost) {
    e[++tot].x = x;
	e[tot].y = y;
    e[tot].cost = cost;
    e[tot].flow = flow;
    e[tot].next = head[x];
	head[x] = tot;
    e[++tot].x = y;
    e[tot].y = x;
    e[tot].cost = -cost;
    e[tot].flow = 0;
    e[tot].next = head[y];
    head[y] = tot;
}

int spfa() {
    std::queue<int> q;
    int x, y;
    memset(dis, 127 / 3, sizeof(dis));
    memset(v, 0, sizeof(v));
    q.push(s);
	dis[s] = 0;
	v[s] = 1;
    while (q.size()) {
        x = q.front();
		q.pop();
		v[x] = 0;
        for (int i = head[x]; i; i = e[i].next) {
            if (!e[i].flow) continue;
            y = e[i].y;
            if (dis[y] > dis[x] + e[i].cost) {
                dis[y] = dis[x] + e[i].cost;
                pre[y] = i;
                if (!v[y]) {
                    v[y] = 1;
                    q.push(y);
                }
            }
        }
    }
    return dis[t] < 707406378;
}

void addflow() {
    int i = t, mn = 2147483647;
    while (pre[i]) {
        mn = std::min(mn, e[pre[i]].flow);
        i = e[pre[i]].x;
    }
	ans += dis[t] * mn;
    i = t;
    while (pre[i]) {
        e[pre[i]].flow -= mn;
        e[pre[i] ^ 1].flow += mn;
        i = e[pre[i]].x;
    }
}

int main() {
	scanf("%d %d %d", &n, &m, &k);
	for (int i = 1; i < n; i++)
		scanf("%d", &d[i]);
	for (int i = 1; i <= m; i++) {
		scanf("%d %d %d", &p[i].t, &p[i].a, &p[i].b);
		last[p[i].a] = std::max(p[i].t, last[p[i].a]);
		down[p[i].b]++;
	}
	for (int i = 1; i < n; i++)
		get[i + 1] = std::max(last[i], get[i]) + d[i];
	for (int i = 1; i <= m; i++)
		ans += get[p[i].b] - p[i].t;
	s = n * 2 + 1;
	s1 = n * 2 + 2;
	t = n * 2 + 3;
	add(s, s1, k, 0);
	for (int i = 1; i < n; i++) {
		add(s1, i + n, d[i], 0);
		add(i + n, i + 1, inf, -down[i + 1]);//改成负的跑最小费用最大流
		add(i + 1, t, inf, 0);
		add(i, i + n, std::max(get[i] - last[i], 0), 0);
	}
	while (spfa())
		addflow();
	printf("%d", ans);
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值