1. 程式人生 > >NOIp2018普及組T3暨洛谷P5017 擺渡車:題解

NOIp2018普及組T3暨洛谷P5017 擺渡車:題解

題目連結:https://www.luogu.org/problemnew/show/P5017

emm,這次的真的不簡單的,T3比T4難?

醉了。。。

蒟蒻肯定沒有其他大佬講的好啊,但肯定盡力,真的敲得嘔心瀝血,求過 。紀念寫出的一道比較經典的線性動規。

分析題意,我(以弱者的角度先看問題) 首先想到的是:排序+貪心。本以為今天如此簡單,結果發現是自己太天真了。。。然而之後發現:並不一定要一次接著一次的發車,所以貪心破產。

之後就有點摸不著頭腦,去打了T4,出於宣洩直接上爆搜,驚奇的發現樣例過了,趕快開心的回來再看T3.

這時候就想:普及深搜,模擬,簽到都出了,這道題多半就是動規了吧,於是,扯了這麼一大堆下面進入正題。

分析:

思路:動規+字首和(但據某些大佬說還可以用斜率優化?在這裡很抱歉我太弱而不會)。

首先我們直切核心——狀態轉移方程。

我們可以設 f [ i ] f[i] 表示i時間前所有人的最小等待時間。

PS:在這裡如果不好確定方程的維數怎麼辦?可以結合資料範圍和空間限制來考慮(多半是,雖然這題好像也可以有二維與t無關的陣列)。

我們可以把每個人的時間都標在一條時間軸上。然後能更直觀的理解。

這裡借用@sooke 大佬的一張圖。

我們假設發車時間為每個來回4min,各位等車的同學如圖中藍點所示。

然後我們假設當前要求的 f [ i ] f[i] 中的 i

= 11 i=11 ,於是下面開始分析。

我們可以發現:如果之前的都已經算出的話,那麼狀態轉移方程可如下所示:

f [ i ] = m i n ( f [ i ] , f [ j ] + ( c n t [ i ] c n t [ j ] ) i ( s u m [ i ] s u m [ j ] ) ) ; f[i]=min(f[i],f[j]+(cnt[i]-cnt[j])*i-(sum[i]-sum[j]));

其中 c n t [ i ] cnt[i] 代表第i時間到達車站的同學的人數, s u m [ i ] sum[i] 代表第i時間到達車站的同學的時間的總和。

j j 即為上一輛車的發車時間。

剛開始我們對於j的範圍,應該能想到是:

0 < = j < = i m 0<=j<=i-m

很明顯的( ⊙ o ⊙ )!j的取值只要小於i並且和i相聚一個往返時間不就行了嗎?

然後的結果是:50分。(官方資料親測)

但是noip都結束了呀同志,我們不能只侷限於50分呀!

所以進行改進:

這時候我們又想:可不可以將j的範圍進一步縮小呢?

但其實是肯定可以的。我們發現j可以:

i 2 m + 1 < = j < = i m i-2m+1<=j<=i-m

為什麼呢?因為如果兩車間的相距時間大於了一趟往返的時間,那麼我們完全可以在兩者中間繼續分割,並不影響原來的答案。

這個時候也是大大的提升了程式的速度,然而:70分(官方資料親測)

淚奔~

and then ,我們可以繼續考慮有沒有什麼可以剪去的無用狀態。

仔細研究發現:當兩次發車之間如果沒有需要等待的同學的話,直接跳過即可。

經過改正:100分(官方親測)

AC程式碼:

#include<cstdio>
#include<cmath>
using namespace std;
int a[501],cnt[4000005],sum[4000005],f[4000005];
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	int Time=0;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		cnt[a[i]]++;
		sum[a[i]]+=a[i];
		Time=fmax(Time,a[i]);
	}
	for(int i=1;i<Time+m;i++)
	{
		cnt[i]+=cnt[i-1];
		sum[i]+=sum[i-1];
	}
	for(int i=0;i<Time+m;i++)
	{
		if (i>=m&&cnt[i-m]==cnt[i])
		{
			 f[i]=f[i-m]; 
			 continue; 
		} 
		f[i]=cnt[i]*i-sum[i];
		int tmp;
		tmp=fmax(i-2*m+1,0); 
		for(int j=tmp;j<=i-m;j++)
		{
			f[i]=fmin(f[i],f[j]+(cnt[i]-cnt[j])*i-(sum[i]-sum[j]));
		}
	}
	int ans=2147483647;
	for(int i=Time;i<Time+m;i++)
	{
		ans=fmin(ans,f[i]);
	}
	printf("%d",ans);
	return 0;
}
完結撒花~