题意
有 N N N个人要坐车,已知车往返一趟要 m m m分钟,还有每个同学去等车的时间,求出最少的等待时间总和。
思路
一看到这个题就觉得是动态规划,不过水平有限,考场上没做出来,之后看了题解才会做的。
我们可以把时间看成一个数轴,如这个洛谷上题解的图
把这个数轴分成若干的长度
≥
M
\geq M
≥M的段,那么等待时间总和就为每一段上所有点到这一段右端点的距离总和。代表从右边这个端点开始出发。那么我们设
f
[
i
]
f[i]
f[i]为设表示对数轴分段,且最后一段的右边界是i,数轴内的点到各自所属段右边界的距离之和最小值。可得动态转移方程:
f
[
i
]
=
m
i
n
j
<
i
−
m
(
f
[
j
]
+
∑
j
<
t
k
≤
i
i
−
t
k
)
f[i]=\underset {j<i-m}{min}(f[j]+\sum_{j<t_k \leq i}^{ }i-t_k)
f[i]=j<i−mmin(f[j]+j<tk≤i∑i−tk)
对这个
∑
j
<
t
k
≤
i
i
−
t
k
\underset{j<t_k \leq i}{\sum}i-t_k
j<tk≤i∑i−tk,我们可以把它拆,变成
(
p
i
−
p
j
)
∗
i
−
(
s
i
−
s
j
)
(p_i-p_j)*i-(s_i-s_j)
(pi−pj)∗i−(si−sj),其中
p
i
p_i
pi为前
i
i
i个时间上有几个点,
s
i
s_i
si为前i个时间上每个
t
i
t_i
ti的总和。
这样的做法有50分。
我们可以发现,如果
i
=
6
,
m
=
2
i=6,m=2
i=6,m=2时,
f
2
f_2
f2可以转移到
f
4
,
f
4
f_4,f_4
f4,f4又可以转移到
f
6
f_6
f6。那么当
j
≤
i
−
2
m
j \leq i-2m
j≤i−2m时,可以转移到
i
−
m
i-m
i−m,由于分段数不会影响答案,所以我们可以从
i
−
2
m
+
1
i-2m+1
i−2m+1转移,避免冗余操作,这优化可以得到70分。
大佬题解还给了个优化:
举个例子,假设正在求 f i f_i fi,但在 ( i − m i-m i−m, i i i] 中没有任何点,这个 f i f_i fi相对来说就是 “无用” 的。原因是若最后一段长度恰好 = m =m =m,这里面又没有任何点,不分割也罢。长度 > m >m >m时,完全可以把这一段的右边界往左“拖”,产生不劣的答案。
那么避免漏解,我们让
f
[
i
]
=
f
[
i
−
m
]
f[i]=f[i-m]
f[i]=f[i−m]。
目标
f
[
i
]
f[i]
f[i]{
i
>
=
M
i>=M
i>=M}
代码
#include<cstdio>
#include<algorithm>
const int MAXL = 4000001;
int point[MAXL], sum[MAXL], f[MAXL];
int N, M, t, ans;
int main() {
scanf("%d %d", &N, &M);
for (int i = 1, x; i <= N; i++) {
scanf("%d", &x);
point[x]++;
sum[x] += x;
t = std::max(t, x);
}
for (int i = 1; i <= t + M; i++) {
point[i] += point[i - 1];
sum[i] += sum[i - 1];
}
ans = 2147483647;
for (int i = 0; i <= t + M; i++) {
if (i >= M && point[i] == point[i - M]) {//无点情况
f[i] = f[i - M];
continue;
}
f[i] = point[i] * i - sum[i];
for (int j = std::max(0, i - M * 2 + 1); j <= i - M; j++)//除去冗余操作
f[i] = std::min(f[i], f[j] + (point[i] - point[j]) * i - (sum[i] - sum[j]));
if (i >= t) ans = std::min(ans, f[i]);
}
printf("%d", ans);
}