【NOIP2018普及组】【DP】摆渡车

果然蒟蒻只会普及组的题…… — — B y   C h a l o t t o ——By\ \mathscr{Chalotto} By Chalotto


题目描述

n n n 名同学要乘坐摆渡车从人大附中前往人民大学,第 i i i 位同学在第 t i t_i ti 分钟去等车。只有一辆摆渡车在工作,摆渡车容量可以视为无限大。摆渡车从人大附中出发、把车上的同学送到人民大学、再回到人大附中(去接其他同学),这样往返一趟总共花费 m m m 分钟(同学上下车时间忽略不计)。摆渡车要将所有同学都送到人民大学。

凯凯很好奇,如果他能任意安排摆渡车出发的时间,那么这些同学的等车时间之和最小为多少呢?

注意:摆渡车回到人大附中后即刻出发。


输入格式

第一行包含两个正整数 n , m n,m n,m ,以一个空格分开,分别代表等车人数和摆渡车往返一趟的时间。

第二行包含 n n n 个正整数,相邻两数之间以一个空格分隔,第 i i i 个非负整数 t i t_i ti 代表第 i i i 个同学到达车站的时刻。


输出格式

输出一行,一个整数,表示所有同学等车时间之和的最小值(单位:分钟)。


样例输入1

5 1
3 4 4 3 5

样例输出1

0

样例说明

同学 1 1 1 和同学 4 4 4 在第 3 3 3 分钟开始等车,等待 0 0 0 分钟,在第 3 3 3 分钟乘坐摆渡车出发。摆渡车在第 4 4 4 分钟回答人大附中。

同学 2 2 2 和同学 3 3 3 在第 4 4 4 分钟开始等车,等待 0 0 0 分钟,在第 4 4 4 分钟乘坐摆渡车出发。摆渡车在第 5 5 5 分钟回到人大附中。

同学 5 5 5 在第 5 5 5 分钟开始等车,等待 0 0 0 分钟,在第 5 5 5 分钟乘坐摆渡车出发。自此所有同学都被送到人民大学。总等待时间为 0 0 0


样例输入2

5 5
11 13 1 5 5

样例输出2

4

样例说明

同学 3 3 3 在第 1 1 1 分钟开始等车,等待 0 0 0 分钟,在第 1 1 1 分钟乘坐摆渡车出发。摆渡车在第 6 6 6 分钟回答人大附中。

同学 4 4 4 和同学 5 5 5 在第 5 5 5 分钟开始等车,等待 1 1 1 分钟,在第 6 6 6 分钟乘坐摆渡车出发。摆渡车在第 11 11 11 分钟回到人大附中。

同学 1 1 1 在第 11 11 11 分钟开始等车,等待 2 2 2 分钟;同学 2 2 2 在第 13 13 13 分钟开始等车,等待 0 0 0 分钟。他/她们在第 13 13 13 分钟乘坐摆渡车出发。自此所有同学都被送到人民大学。总等待时间为 4 4 4 。可以证明,没有总等待时间小于 4 4 4 的方案。


数据规模与约定

对于 10 % 10\% 10% 的数据, n ≤ 10 , m = 1 , 0 ≤ t i ≤ 100 n\le 10,m=1,0\le t_i\le 100 n10,m=1,0ti100
对于 30 % 30\% 30% 的数据, n ≤ 20 , m ≤ 2 , 0 ≤ t i ≤ 100 n\le 20,m\le 2,0\le t_i\le 100 n20,m2,0ti100
对于 50 % 50\% 50% 的数据, n ≤ 500 , m ≤ 100 , 0 ≤ t i ≤ 1 0 4 n\le 500,m\le 100,0\le t_i\le 10^4 n500,m100,0ti104
另有 20 % 20\% 20% 的数据, n ≤ 500 , m ≤ 10 , 0 ≤ t i ≤ 4 × 1 0 6 n\le 500,m\le 10,0\le t_i\le 4\times 10^6 n500,m10,0ti4×106
对于 100 % 100\% 100% 的数据, n ≤ 500 , m ≤ 100 , 0 ≤ t i ≤ 4 × 1 0 6 n\le 500,m\le100,0\le t_i\le 4\times 10^6 n500,m100,0ti4×106


?解析?

显然证明法就是根据题目的意思进行直觉性判断,从而证明结论成立。

显然,这是一道 D P DP DP 题。
于是设 f [ i ] f[i] f[i] 表示第 i i i 分钟发出一辆车所要的最小时间,设 t t t 为最后一个人到达车站的时间。

得到状态转移方程: f [ i ] = m i n { f [ k ] + ∑ k < a [ j ] ≤ i i − a [ j ] } ( 0 ≤ k ≤ i − m ) f[i]=min\{f[k]+\sum_{kf[i]=min{f[k]+k<a[j]iia[j]}(0kim)其中 k k k 表示上一辆末班车回来的时间。

那么最后的答案即为: a n s = m i n { f [ i ] } ( t ≤ i < t + m ) ans=min\{f[i]\}(t\le ians=min{f[i]}(ti<t+m)
i < t + m ii<t+m 是因为最后一个人等的时间不会超过 m m m 分钟,不然他可以直接乘之前的一辆走,肯定不是最优解。

时间复杂度: Θ ( t 2 n ) \Theta(t^2n) Θ(t2n)

优化

1、前缀和优化

c n t [ i ] cnt[i] cnt[i] 表示到 i i i 为止到达车站的人数和
s u m [ i ] sum[i] sum[i] 表示到 i i i 为止到达车站的人的时间总和
显然有: ∑ k < a [ j ] ≤ i i − a [ j ] = i ∗ ( c n t [ i ] − c n t [ k ] ) − ( s u m [ i ] − s u m [ k ] ) \sum_{kk<a[j]iia[j]=i(cnt[i]cnt[k])(sum[i]sum[k])即,总等待时间 = = = i × ( i ∼ k i\times (i\sim k i×(ik 内总人数 ) ) ) − - 等待时间在 i ∼ k i\sim k ik 内所有人的等待时间之和。

附上一张图帮助理解:
在这里插入图片描述
于是方程变为: f [ i ] = m i n { f [ k ] + i ∗ ( c n t [ i ] − c n t [ k ] ) − ( s u m [ i ] − s u m [ k ] ) } ( k ≤ i − m ) f[i]=min\{f[k]+i*(cnt[i]-cnt[k])-(sum[i]-sum[k])\}(k\le i-m) f[i]=min{f[k]+i(cnt[i]cnt[k])(sum[i]sum[k])}(kim)

时间复杂度: Θ ( t 2 ) \Theta(t^2) Θ(t2)

2、转移优化

显然,没有一位同学的等待时间会超过 2 m 2m 2m 分钟。

简单证明:
考虑最坏的情况,在 k k k 时刻发了一辆车,有个同学在 k + 1 k+1 k+1 时到达车站,那么摆渡车将在 k + m k+m k+m 时返回,考虑到等待其它学生的情况,摆渡车最晚会在 k + 2 m − 1 k+2m-1 k+2m1 时发车,否则还不如分别在 k + m k+m k+m k + 2 m k+2m k+2m 的时候发出。

所以, k k k 的范围就变为: i − 2 m < i ≤ i − m i-2mi2m<iim

时间复杂度: Θ ( t m ) \Theta(tm) Θ(tm)
完全不用虚了(́순◞౪◟순‵)


代码展示

#include
#define ll long long
#define me(x) memset(x,0,sizeof(x))
#define bi(x) memset(x,0x3f,sizeof(x))
#define ud  using namespace std
ud;
const int maxn=1e7+10; int last_time=-9876543,ans=1<<30;
int N,M,t[maxn],f[maxn],sum[maxn]={},cnt[maxn]={};
inline long long read()
{
	long long s=0,f=1; char ch; for(;ch<'0' || ch>'9';ch=getchar()) if(ch=='-') f=-1;
	for(;ch>='0' && ch<='9';ch=getchar()) s=(s<<1)+(s<<3)+ch-'0'; return s*f;
}
int main()
{
	N=read(),M=read();
	for(int i=1;i<=N;++i) 
	{
		t[i]=read();
		// Find the last person who reach the station
		last_time=max(last_time,t[i]);
		++cnt[t[i]];//从0到i时间为止到达车站的人数和
		sum[t[i]]+=t[i];//从0到i时间为止到达车站的人的时间总和
	}
	for(int i=1;i<last_time+M;++i)
	{
		cnt[i]+=cnt[i-1];
		sum[i]+=sum[i-1];
	}
	for(int i=0;i<last_time+M;++i)
	{
		if(i>=M&&cnt[i-M]==cnt[i])
		{
			f[i]=f[i-M]; continue;
		}
		f[i]=cnt[i]*i-sum[i];
		for(int j=max(i-(M<<1)+1,0);j<=i-M;++j)
		f[i]=min(f[i],f[j]+(cnt[i]-cnt[j])*i-(sum[i]-sum[j]));
	} 
	for(int i=last_time;i<last_time+M;++i) ans=min(ans,f[i]);
	printf("%d\n",ans);
	return 0;
}

你可能感兴趣的:(DP专题)