NOIP2018普及组——摆渡车

NOIP2018普及组——摆渡车

日期:2020-08-22

一、题意分析

题目链接

  1. 任务:有\(n\)位同学,第\(i\)为同学在时刻等车。已知摆渡车往返一趟的时间为\(m\)分钟,问怎么安排摆渡车发车时间,使得同学们的等车时间之和最小,求等车时间之和的最小值。
  2. 数据范围:\(n≤500\)\(m≤100\)\(0≤t_i≤4×10^6\)

二、算法分析

1. 分析

显然的,这是个\(DP\),于是我们能给出定义和所求:

1). 定义

\(f(i)\)表示在第\(i\)时刻发车、\(0-i\)时刻内等车时间和的最小值。

2). 所求

设等车的最晚时间为\(tmax\),则\(\min_{i=t_{max}}^{t_{max}+m-1}f(i)\)即为所求。

3). 转移方程

对于转移方程,我们需要略加分析:显然的,要想在第\(i\)时刻发车,上次的发车时间一定在第\(i-m\)时刻或之前。事实上,我们只用枚举\((i-2m,i-m]\)这些发车时刻,并求出最小值。这是因为,如果我们在\(i-2m\)时刻前发了一趟车,那么在本次发车以前,还可以再发一辆车;而对于本题,如果合法,发车总比不发车好。

设第\(i\)时刻等车的人数为\(cnt_i\),那么我们可以得到如下方程:

\[f(i)= \begin{cases} \min_{j=\max(0,i-2m+1)}^{i-m}(f(j)+\sum_{k=j+1}^{i}(i-k) \times cnt_k) & i \geq m \\ \sum_{j=0}^{i}(i-j) \times cnt_j & 0 \leq i < m \end{cases} \]

于是,我们就想出了一个时间复杂度为\(O(m^2t)\)的算法。对于此题,我们仅能拿到\(50pts\)

2. 优化

1). 优化\(1\)

我们觉得\(\sum\)很碍眼,希望优化掉它。于是,我们很容易就想到了前缀和。设在\(0-i\)时刻中,一共有\(cnt_i\)人在等车;而他们的出现时间之和为\(sum_i\)

于是,我们得到了一个新的方程:

\[\begin{cases} \min_{j=\max(0,i-2m+1)}^{i-m}(f(j)+(cnt_i - cnt_j) \times i-(sum_i-sum_j)) & i \geq m \\ cnt_i \times i - sum[i] & 0 \leq i < m \end{cases} \]

至此,我们有了一个\(O(mt)\)的算法,可以多拿\(20pts\)

2). 优化\(2\)

进一步分析可以看出,时间很大,人数却很少。于是,我们想到:若在第\(0-i\)时刻等车的总人数与在第\(i-m\)时刻的等车总人数相等(即\(cnt_i=cnt_{i-m}\)时),他们的结果相等(即\(f(i)=f(i-m)\))。这是因为,如果在\((i-m,m]\)时刻没有新的乘客的话,我们没有必要再发一趟车,在实际生活中也是如此。

于是,我们得到最终的方程:

\[\begin{cases} \min_{j=\max(0,i-2m+1)}^{i-m}(f(j)+(cnt_i - cnt_j) \times i-(sum_i-sum_j)) & i \geq m &and & cnt_i \neq cnt_{i-m} \\ f(i-m) & i \geq m & and & cnt_i=cnt_{i-m}\\ cnt_i \times i - sum[i] & 0 \leq i < m \end{cases} \]

最后,我们得到了这个\(O(mn^2+t)\)的算法,可以\(AC\)此题。

三、完整代码

#include <bits/stdc++.h>
using std::max;
using std::min;

const int T = 4400000;
int cnt[T], sum[T], f[T];
int n, m, x;

int main(){ 
	scanf("%d%d", &n, &m);
	
	memset(cnt, 0, sizeof cnt);
	memset(sum, 0, sizeof sum);
	int maxt = 0;
	
	for (int i = 1; i <= n; ++i) { 
		scanf("%d", &x);
		++cnt[x];
		sum[x] += x;
		if (maxt < x) maxt = x;
	} 
	
	for (int i = 1; i < maxt + m; ++i) { 
		cnt[i] += cnt[i - 1];
		sum[i] += sum[i - 1];
	} 
	
	memset(f, 0x3f, sizeof f);
	for (int i = 0; i < m; ++i) f[i] = cnt[i] * i - sum[i];
	
	for (int i = m; i < maxt + m; ++i) { 
		if (cnt[i] == cnt[i - m]) { 
			f[i] = f[i - m];
			continue;
		} 
		
		for (int j = max(0, i - (m << 1) + 1); j <= max(0, i - m); ++j) 
			f[i] = min(f[i], f[j] + (cnt[i] - cnt[j]) * i - (sum[i] - sum[j]));
	} 
	
	int ans = f[maxt];
	for (int i = maxt + 1; i < maxt + m; ++i) ans = min(ans, f[i]);
	
	printf("%d\n", ans);
	
	return 0;
} 
posted @ 2020-08-23 21:52  _lhy  阅读(551)  评论(0编辑  收藏  举报