您的位置:首页 > 其它

NOIP 2011 Senior 6 - 观光公交

2017-07-06 19:16 495 查看
观光公交

总时间限制: 1000ms 内存限制: 65535kB

描述

风景迷人的小城 Y 市,拥有 n 个美丽的景点。由于慕名而来的游客越来越多,Y 市特意安排了一辆观光公交车,为游客提供更便捷的交通服务。观光公交车在第 0 分钟出现在 1 号景点,随后依次前往 2、3、4……n号景点。从第 i 号景点开到第 i+1 号景点需要 Di分钟。任意时刻,公交车只能往前开,或在景点处等待。

设共有 m 个游客,每位游客需要乘车 1 次从一个景点到达另一个景点,第 i 位游客在 Ti分钟来到景点 Ai,希望乘车前往景点 Bi(Ai<Bi)。为了使所有乘客都能顺利到达目的地,公交车在每站都必须等待需要从该景点出发的所有乘客都上车后才能出发开往下一景点。假设乘客上下车不需要时间。

一个乘客的旅行时间,等于他到达目的地的时刻减去他来到出发地的时刻。因为只有一辆观光车,有时候还要停下来等其他乘客,乘客们纷纷抱怨旅行时间太长了。于是聪明的司机 ZZ 给公交车安装了 k个氮气加速器,每使用一个加速器,可以使其中一个 Di减1。对于同一个 Di可以重复使用加速器,但是必须保证使用后 Di大于等于 0。那么 ZZ 该如何安排使用加速器,才能使所有乘客的旅行时间总和最小?

输入

第 1 行是3个整数 n, m, k,每两个整数之间用一个空格隔开。分别表示景点数、乘客数和氮气加速器个数。

第 2 行是 n-1 个整数,每两个整数之间用一个空格隔开,第 i 个数表示从第 i 个景点开往第 i+1 个景点所需要的时间,即 Di。

第 3 行至 m+2 行每行3 个整数 Ti, Ai, Bi,每两个整数之间用一个空格隔开。第 i+2 行表示第 i 位乘客来到出发景点的时刻,出发的景点编号和到达的景点编号。

输出

共一行,包含一个整数,表示最小的总旅行时间。

样例输入

3 3 2

1 4

0 1 3

1 1 2

5 2 3

样例输出

10

输入输出样例说明

对 D2 使用 2 个加速器,从 2 号景点到 3 号景点时间变为 2 分钟。

公交车在第 1 分钟从 1 号景点出发, 第 2 分钟到达2号景点, 第 5 分钟从 2 号景点出发,

第 7 分钟到达 3 号景点。

第 1 个旅客旅行时间 7-0 = 7 分钟。

第 2 个旅客旅行时间 2-1 = 1 分钟。

第 3 个旅客旅行时间 7-5 = 2 分钟。

总时间 7+1+2 = 10 分钟。

数据范围

对于 10%的数据,k=0;

对于 20%的数据,k=1;

对于 40%的数据,2 ≤ n ≤ 50,1 ≤m≤ 1,000,0 ≤ k ≤ 20,0 ≤ Di ≤ 10,0 ≤ Ti ≤ 500;

对于 60%的数据,1 ≤ n ≤ 100,1 ≤m≤ 1,000,0 ≤ k ≤ 100,0 ≤ Di ≤ 100,0 ≤ Ti ≤ 10,000;

对于 100%的数据,1 ≤ n ≤ 1,000,1 ≤ m ≤ 10,000,0 ≤ k ≤ 100,000,0 ≤ Di ≤ 100,0 ≤ Ti ≤ 100,000。

这道题的正确思路其实是贪心,但是到底该怎么贪心是关键。一开始,我计算的是对人最多的路使用加速器,但是这显然不正确。经过分析发现,旅游总时间是由旅客下车时间决定的。加速器是否有效取决于你是否能让人提前下车。

因此思路就出来了:让一次加速使尽量多的人提前下车。我们可以计算出每个站点的最早出发时间
earliest
(也就是每个站点最后一个到的时间),并且根据这个计算出不使用加速器时每个站点的实际到达时间
arrival


for (int i = 2; i <= n; i++)
{
arrival[i] = std::max(arrival[i - 1], earliest[i - 1]) + d[i - 1];
}


这些都不是问题,关键是如何找到最佳使用加速器的地方。考虑这样一条路:

d1  n1  d2 n2 d3 n3
------0------0-----
4000
-0
人等车  人等车  车等人


如果我们加速d1,那么在n1,n2,n3下车的人都可以提前下车。因此我们可以这样找到要加速的路:找到一段连续的站点(这些站点除了最后一个站点可以是车等人,其余的都是人等车)使这些站下车的总人数最多,然后对这些站中从左向右数第一个站的前一条路使用加速器。对于以上例子来说,连续的人等车的站就是n1,n2,n3,要使用加速器的路就是d1。

上述的 计算连续的人等车的站点下车的人数 就是计算加速某一段路所影响的人数的方法,当然,如果后面还有一个车等人的站点也不要忘了加上,因为它也要受影响,只是它之后的站点不受影响了而已。

由于使用加速器后不会导致某一个站下车的人发生变化,因此一直以这种策略加速就是最优解。

当然,如果全部都是车等人,那就不管每个站到底是谁等谁,只统计单个点就可以了,因为在这种情况下对任意一条路使用加速器都只会影响一个点。

参考代码

#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <deque>
#include <map>
#include <set>
using std::cin;
using std::cout;
using std::endl;
inline int readIn()
{
int a;
scanf("%d", &a);
return a;
}

const int maxn = 1005;
const int maxm = 10005;
int n, m, k;
int d[maxn];
int start[maxm];
int from[maxm];
int to[maxm];
int earliest[maxn]; //每个点最早的出发时间(最晚乘客的到达时间)
int arrival[maxn];
int getOff[maxn];

void run()
{
n = readIn();
m = readIn();
k = readIn();
for (int i = 1; i <= n - 1; i++)
{
d[i] = readIn();
}
for (int i = 1; i <= m; i++)
{
start[i] = readIn();
from[i] = readIn();
to[i] = readIn();
earliest[from[i]] = std::max(earliest[from[i]], start[i]);
getOff[to[i]]++;
}
for (int i = 2; i <= n; i++)
{
arrival[i] = std::max(arrival[i - 1], earliest[i - 1]) + d[i - 1];
}

while (k--)
{
int sum = 0;
int maxi = 0;
int maxindex = 0;
for (int i = n; i >= 2; i--) //由于要求出连续的人等车的前一段路,因此倒着统计
{
if (earliest[i] >= arrival[i]) //车等人,清零
sum = 0;
sum += getOff[i]; //根据分析,应该后写这句代码
if (d[i - 1] > 0 && sum > maxi)
{
maxi = sum;
maxindex = i - 1;
}
}
if (!maxindex) break; //没有任何一段路可以加速
d[maxindex]--;
for (int i = 2; i <= n; i++) //维护
{
arrival[i] = std::max(arrival[i - 1], earliest[i - 1]) + d[i - 1];
}
}

for (int i = 2; i <= n; i++)
{
arrival[i] = std::max(arrival[i - 1], earliest[i - 1]) + d[i - 1];
}
int ans = 0;
for (int i = 1; i <= m; i++)
{
ans += arrival[to[i]] - start[i];
}
printf("%d", ans);
}

int main()
{
run();
return 0;
}


可以说,我好久没有做过贪心了,像这种一条路的倒是很容易想到DP(DP恐惧症)。不过随着能力的增长,可以很明显的发现这道题不是DP,因为根本描述不出状态。这个时候,还是要考虑下贪心算法,仔细证明一下贪心的依据。尽管贪心在刚开始学习DP时可以说是一种送死的算法,但是联赛又不是只考DP,贪心也是一个重要考点:我们将在以后的题目中继续看见贪心的影子。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: