P5017 NOIP2018 普及组 摆渡车
P5017 NOIP2018 普及组 摆渡车 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
显然要把人按照到达时间排序。然后考虑 dp。
设 \(f(i)\) 表示前 \(i\) 个人已上车或到达目的地,所需最少等待时间。
刷表转移,从 \(f(i)\) 转移到 \(f(j)\),\(j > i\)。而且转移有个条件:我们让公交车接完前 \(i\) 个人回来的时候一次性把 \([i + 1, j]\) 这些人全部接走,也就是一趟搞定,否则会造成转移重复,而且如果运好多趟你会写吗。
很显然,\(f(j)\) 等于 \(f(i)\) 加上 \([i + 1, j]\) 这些人的总等待时间。要想知道这些人的总等待时间,就得知道接 \([i +1, j]\) 的这次公交什么时候发车。
那什么时候发车呢?肯定得等到 \(t[j]\) 来了才能发车,但是如果 \(t[j]\) 来的时候,公交车接完前 \(i\) 个人还没回来怎么办?
所以,我们假设公交车接完前 \(i\) 个人回来时,是时刻 \(T'\),那么公交车的发车时间就应该是 \(T = \max(T', t[j])\)。显然不能比这个更早,也不会比这个更晚。
那公交车接完前 \(i\) 个人回来又是什么时候?是 \(t[i] + m\) 吗?并不是,因为上一次接第 \(i\) 个人的那次公交车,可能比第 \(i\) 个人晚到,也就是还得考虑第 \(i\) 个人等待了一会。
所以顺利成章更换 dp 状态:设 \(f(i, k)\) 表示前 \(i\) 个人已上车或达到目的地,第 \(i\) 个人等了 \(k\) 分钟时,所需最少等待时间。
那么公交车接完前 \(i\) 个人回来是 \(T' = t[i] + k + m\),接 \([i + 1, j]\) 这批人的公交车的发车时间就是 \(T = \max(t[i] + k + m, t[j])\),这些人人的等待时间为 \(\sum\limits_{x = i + 1} ^ j T - t[x]\)。拆成 \(T \times (j - i) - \sum t[i + 1 \cdots j]\),就变成了一个 \(\mathcal{O}(1)\) 可以计算的式子了(后面那个和,循环同时统计即可,或者直接前缀和优化)。
喜提转移方程一枚:
其中 \(a \stackrel{\min}{\gets} b\) 意为 \(a \gets \min(a, b)\)。
\(f(i, k)\) 中 \(k\) 的范围:\([0, \min(m - 1, t[i + 1] - t[i])]\)。这是因为如果 \(t[i]\) 等车等到 \(t[i + 1]\) 一起来了,那么这次来车肯定要把 \(t[i + 1]\) 一起接走而不是只接走 \(t[i]\)(第 \(i + 1\) 个人:你最好有事)。
初始状态:\(t[0] = -\infty\),\(f(0, 0) = 0\),其它 \(f\) 均为 \(+\infty\)。
一个细节:本题最大等待时间上界达不到 \(nt\),而是 \(2nm\)(严格小于这个),这是因为最优方案中每个人等待时间一定小于 \(2m\)。
时间复杂度 \(\mathcal{O}(n^2m)\)。
/*
* @Author: crab-in-the-northeast
* @Date: 2022-11-11 00:44:12
* @Last Modified by: crab-in-the-northeast
* @Last Modified time: 2022-11-11 01:22:25
*/
#include <bits/stdc++.h>
inline int read() {
int x = 0;
bool f = true;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-')
f = false;
for (; isdigit(ch); ch = getchar())
x = (x << 1) + (x << 3) + ch - '0';
return f ? x : (~(x - 1));
}
inline int max(int a, int b) {
return a > b ? a : b;
}
inline int min(int a, int b) {
return a < b ? a : b;
}
inline bool gmi(int &a, int b) {
return b < a ? a = b, true : false;
}
const int maxn = 505;
const int maxm = 105;
int t[maxn], f[maxn][maxm];
int main() {
int n = read(), m = read();
for (int i = 1; i <= n; ++i)
t[i] = read();
std :: sort(t + 1, t + 1 + n);
t[0] = -105;
std :: memset(f, 0x3f, sizeof(f));
f[0][0] = 0;
for (int i = 0; i <= n; ++i) {
for (int k = 0; k <= min(m - 1, t[i + 1] - t[i]); ++k) {
for (int j = i + 1, tot = t[i + 1]; j <= n; ++j, tot += t[j]) {
int T = max(t[i] + k + m, t[j]);
gmi(f[j][T - t[j]],
f[i][k] + T * (j - i) - tot);
}
}
}
int ans = INT_MAX;
for (int k = 0; k < m; ++k)
gmi(ans, f[n][k]);
printf("%d\n", ans);
return 0;
}