P5017 [NOIP2018 普及组] 摆渡车
P5017 [NOIP2018 普及组] 摆渡车
题目
P5017
思路
将实际问题抽象后,不难发现这是一个 区间 \(DP\)
我们不妨认为时间是一条数轴,每名同学按照到达时刻分别对应数轴上可能重合的点。安排车辆的工作,等同于将数轴分成若干个左开右闭段,每段的长度 \(\geqslant m\)。原本的等车时间之和,自然就转换成所有点到各自所属段右边界的距离之和。
转移: \(f_i=min\{f_j+\sum_{j
但是这样显然时间复杂度会超标
考虑使用前缀和优化掉那个大大的 \(\sum\)
之后,转移式可以这样写: \(f_i=min\{f_j+(cnt_i-cnt_j)*i-(sum_i-sum_j)\}\) \(,\) \(j\leq i-m\)
这里令 \(t=max\{t_i\}\) \(,\) \(1\leq i \leq n\),最终答案只需在 \(i \geqslant t\) 找最小的 \(f_i\) 即可。实际上,\([t, t+m)\) 包含了所有可能的答案。
此时考虑时间复杂度:\(O(n^2)\) 非常不合理
考虑优化 \(DP\)
仍然考虑 \((j,i]\) 段的长度,由于分的段数不会增大答案,当它的长度 \(\geqslant 2m\) 时,我们完全可以再给它切一刀,得到不劣的答案。通过此性质,可剪去大量无用转移。
此时再来考虑时间复杂度:\(O(tm)\) 还是不够优秀 只能达到70pts
再考虑优化 \(DP\)
假设正在求 \(f_i\),但在 \((i-m,i]\) 中没有任何点,这个 \(f_i\) 相对来说就是 “无用” 的。原因是若最后一段长度恰好 \(= m\),这里面又没有任何点,不分割也罢。长度 \(>m\) 时,完全可以把这一段的右边界往左“拖”,产生不劣的答案。
然而直接扔掉这个状态,会与上一个优化缩小转移范围起冲突,故无用的位置令 \(f_i = f_{i-m}\),防止漏解。
此时的时间复杂度就已经非常优秀了:\(O(nm^2+t)\) 稳定100pts
总结
这是一道非常好的区间类 \(DP\) 问题,值得反复思考
CPP
#include
using namespace std;
const int N=4e6+10;
const int INF=1e9;
int n,m,T;
int a[N],f[N],s[N];
inline int max(int a,int b) {
return a>b?a:b;
}
inline int min(int a,int b) {
return a= '0' && c <= '9')) if (c == '-') f = -1;
x = c - '0';
while ((c = getchar()) >= '0' && c <= '9') (x *= 10) += c - '0';
return x * f;
}
inline void write(int x) {
if (x < 0) putchar('-'), x = -x;
if (x > 9) write(x / 10);
putchar(x % 10 ^ 48);
}
int main() {
n=read(),m=read();
for(int i=1; i<=n; i++) {
int t=read();
a[t]++;
s[t]+=t;
T=max(T,t);
}
for(int i=1; i=m && a[i-m]==a[i]) {
f[i]=f[i-m];
continue;
}
f[i]=a[i]*i-s[i];
for(int j=max(0,i-(m<<1)+1); j<=i-m; j++)
f[i]=min(f[i],f[j]+(a[i]-a[j])*i-(s[i]-s[j]));
}
int ans=INF;
for(int i=T; i