Luogu 1315 【NOIP2011】觀光公交 (貪心)


Luogu 1315 【NOIP2011】觀光公交 (貪心)

Description

風景迷人的小城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 該如何安排使用加速器,才能使所有乘客的旅行時間總和最小?

Input

第1 行是3 個整數n, m,k,每兩個整數之間用一個空格隔開。分別表示景點數、乘客數和氮氣加速器個數。
第2 行是n-1 個整數,每兩個整數之間用一個空格隔開,第i個數表示從第i個景點開往第i+1 個景點所需要的時間,即Di。
第3 行至m+2 行每行3 個整數Ti, Ai,Bi,每兩個整數之間用一個空格隔開。第i+2 行表示第i 位乘客來到出發景點的時刻,出發的景點編號和到達的景點編號。

Output

共一行,包含一個整數,表示最小的總旅行時間。

Sample Input

3 3 2
1 4
0 1 3
1 1 2
5 2 3

Sample Output

10

Http

Luogu:https://www.luogu.org/problem/show?pid=1315

Source

貪心

解決思路

首先可以貪心的想到,要讓每一個加速器的效應最大,那么要讓其作用的人最多,所以我們可以想到是一個取最大值的操作。
再來考慮每一個景點。首先如果當前公交車到達某一個景點后還要等乘客,這時在前面這條路上使用加速器是不實用的,因為同樣還要等,沒有必要浪費。即我們可以得出一個結論,就是公交車從一個景點出發的理論最晚時間(LastTime)就是這個景點的最后來的那個人的時間。
所以,我們可以先把不使用加速器的總時間算出來,然后再最優地減去最多的時間。
不使用加速器的總時間是多少呢?

\[Ans=\sum_{i-1}^{m}LeaveTime[Ranger[i]]-PeopleTime[i] \]

\(Ranger[i]\)代表第i個人的終點景點
\(PeopleTime[i]\)代表第i個人到來的時間
\(LeaveTime[i]\)表示從i點出發的實際時間,那么\(LeaveTime[i]=max(LeaveTime[i-1],LastTime[i-1])+Dist[i-1]\)
其中\(Dist[i-1]\)表示第i-1個景點到景點i消耗的時間。
從計算總時間的式子可以看出,\(PeopleTime[i]\)是不會變的,我們能減少的就是\(LeaveTime[]\)這一部分。
也就是說,我們要使得每一個人盡可能地早下車
然后考慮如何取這個最優
首先需要知道的是,如果我們對一條路上的時間-1,那么它影響的不僅僅只是這條路,還可能會影響到后面的若干條路。如果\(LeaveTime[i]>LastTime[i]\),也就是說在第i個點上,最晚來的那個人也要等一下,那么如果公交車來早1,這個景點的所有人就都可以早出發1,這也就導致后面的時間都會提早1,向后傳遞。否則,若在第i個景點,公交車本來就要等最后一個人來,那么此時公交車來早1,同樣還是要等到那個時間,后面的時間不會因此改變,所以不會影響后面。
具體在實現上面的計算時,我們用LastInfluence[i]代表第i+1個景點最遠能影響到的景點。為什么這里是第i+1呢?因為實際上我們在計算影響時是根據兩點之間的邊來算的,但后面統計又要用到人,所以把邊換成對點的操作。
那么對於從i到\(LastInfluence[i]\)這其中我們要統計什么呢?
結合我們上面所說的讓每個人盡早下車,我們統計一下每一個景點下車的人數,那么如果我們把第i條邊的時間-1,那么對於景點i到景點\(LastInfluence[i]\)這其中所有下車的人的時間都-1。那么現在的問題就是要找出這些影響區間所覆蓋的下車人數最大。簡單來說,就是區間最大和。這個最大和,我們可以用前綴和來維護。
注意,每使用一次加速器,要把LeaveTime[i]重新統計,因為使用加速器后會有一條邊的時間減少,對應的會影響一段區間的時間,所以要重新計算。
簡單地總結一下,此題的步驟就是

1.求出不使用加速器時的總時間
2.每一次找出一個影響區間覆蓋的下車人數最多的區間,減去對應的答案,並重新統計實際出發時間,以此進行下次求值。

代碼

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;

const int maxN=2000;
const int maxM=20000;
const int inf=2147483647;

int n,m,K;
int Dist[maxN];
int EndSum[maxN];//EndSum[i]表示在i號位置下車的人的數量,輸入完后,再用來計算前綴和
int Rangel[maxM];//人出發的景點
int Ranger[maxM];//人到達的景點
int PeopleTime[maxM];//人到達景點的時間
int LastTime[maxN];//最后到達的人的時間
int LeaveTime[maxN];//離開的時間
int LastInfluence[maxN];//影響區間

void init();//計算LeaveTime數組

int main()
{
	memset(LastTime,0,sizeof(LastTime));
	memset(EndSum,0,sizeof(EndSum));
	scanf("%d%d%d",&n,&m,&K);
	for (int i=1;i<n;i++)
		scanf("%d",&Dist[i]);
	Dist[n]=0;
	for (int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&PeopleTime[i],&Rangel[i],&Ranger[i]);
		EndSum[Ranger[i]]++;
		LastTime[Rangel[i]]=max(LastTime[Rangel[i]],PeopleTime[i]);
	}
	int Ans=0;
	for (int i=1;i<=n;i++)//計算前綴和
		EndSum[i]+=EndSum[i-1];
	init();
	for (int i=1;i<=m;i++)//統計不使用加速器時的總時間
		Ans+=LeaveTime[Ranger[i]]-PeopleTime[i];
	while (K--)
	{
		LastInfluence[n]=LastInfluence[n-1]=n;//倒着推出每一個景點向后最遠能影響到哪里
		for (int i=n-2;i>=1;i--)
			LastInfluence[i]=(LeaveTime[i+1]<=LastTime[i+1])?i+1:LastInfluence[i+1];//感謝@淚眼問花花不語 找到一個錯誤
		int nowmax=0,id;//求出最大區間和
		for (int i=1;i<n;i++)
			if ((EndSum[LastInfluence[i]]-EndSum[i]>nowmax)&&(Dist[i]!=0))
			{
				nowmax=EndSum[LastInfluence[i]]-EndSum[i];
				id=i;
			}
		Dist[id]--;//減去使用加速器造成的效果
		Ans-=nowmax;
		init();//重新計算LeaveTime
	}
	printf("%d\n",Ans);
	return 0;
}

void init()
{
	LeaveTime[1]=LastTime[1];
	for (int i=2;i<=n;i++)
		LeaveTime[i]=max(LastTime[i-1],LeaveTime[i-1])+Dist[i-1];
	return;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM