NOIP2018普及组——摆渡车
NOIP2018普及组——摆渡车
日期:2020-08-22
一、题意分析
- 任务:有\(n\)位同学,第\(i\)为同学在时刻等车。已知摆渡车往返一趟的时间为\(m\)分钟,问怎么安排摆渡车发车时间,使得同学们的等车时间之和最小,求等车时间之和的最小值。
- 数据范围:\(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\),那么我们可以得到如下方程:
于是,我们就想出了一个时间复杂度为\(O(m^2t)\)的算法。对于此题,我们仅能拿到\(50pts\)。
2. 优化
1). 优化\(1\)
我们觉得\(\sum\)很碍眼,希望优化掉它。于是,我们很容易就想到了前缀和。设在\(0-i\)时刻中,一共有\(cnt_i\)人在等车;而他们的出现时间之和为\(sum_i\)。
于是,我们得到了一个新的方程:
至此,我们有了一个\(O(mt)\)的算法,可以多拿\(20pts\)。
2). 优化\(2\)
进一步分析可以看出,时间很大,人数却很少。于是,我们想到:若在第\(0-i\)时刻等车的总人数与在第\(i-m\)时刻的等车总人数相等(即\(cnt_i=cnt_{i-m}\)时),他们的结果相等(即\(f(i)=f(i-m)\))。这是因为,如果在\((i-m,m]\)时刻没有新的乘客的话,我们没有必要再发一趟车,在实际生活中也是如此。
于是,我们得到最终的方程:
最后,我们得到了这个\(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;
}