## [$AcWing$ $475$. 摆渡车](https://www.acwing.com/problem/content/description/477/) ### 一、题目描述 有$n$名同学要乘坐摆渡车从 人大附中 前往 人民大学,第 $i$ 位同学在第 $t_i$分钟去等车。只有一辆摆渡车在工作,但摆渡车容量可以视为无限大。摆渡车从人大附中出发, 把车上的同学送到人民大学,再回到人大附中(去接其他同学),这样往返一趟总共花费$m$分钟(同学上下车时间忽略不计)。摆渡车要将所有同学都送到人民大学。 凯凯很好奇,如果他能任意安排摆渡车出发的时间,那么这些同学的 **等车时间之和最小** 为多少呢? **注意**:摆渡车回到人大附中后可以即刻出发。 **输入格式** 第一行包含两个正整数 $n,m$,以一个空格分开,分别代表等车人数和摆渡车往返一趟的时间。 第二行包含 $n$ 个正整数,相邻两数之间以一个空格分隔,第 $i$ 个非负整数 $t_i$代表第$i$个同学到达车站的时刻。 **输出格式** 输出一行,一个整数,表示所有同学等车时间之和的最小值(单位:分钟)。 **输入样例** ```cpp {.line-numbers} 5 5 11 13 1 5 5 ``` **输出样例** ```cpp {.line-numbers} 4 ``` ### 二、解题思路 如果把时间看作一个数轴,那么所有同学到达车站的时刻就是数轴上的点,安排车辆的工作就是把数轴划分成若干个 **左开右闭** 的子区间,如下图所示: ![](https://dsideal.obs.cn-north-1.myhuaweicloud.com/HuangHai/BlogImages/{year}/{month}/{md5}.{extName}/202310091646906.png) 例如测试样例中摆渡车安排在时刻$1、6、13$时,所有同学的等待时间之和最少,为$4$。一共安排了$3$次乘车,将数轴划分$3$段左开右闭的区间,分别是:$(0,1],(1,6],(6,13]$,每段长度都$≥m$,上图中$m=5$。 这样,等待时间之和就转换成了所有点到各自所属区间右边界的距离之和。 #### 状态表示 设$f[i]$表示数轴上对于前$i$个点,且最后一段的右边界为$i$,位于$(0,i]$的所有点到各自所属区间右边界的 **距离之和的最小值**。 #### 状态转移 设最后一段是$(j,i]$,而每段长度$i−j≥m$,则有$j≤i−m$。如果第$k$位同学到达时间$t[k]$属于区间$(j,i]$,即$j using namespace std; // 通过了 10/20个数据 const int N = 4000010; const int INF = 0x3f3f3f3f; int f[N], cnt[N], sum[N]; int n, m; // 等车人数和摆渡车往返一趟的时间 int T; // T表示最后一个同学到达车站的时间 int main() { cin >> n >> m; for (int i = 1; i <= n; i++) { int t; // 每个同学到达车站的时刻 cin >> t; T = max(T, t); // 最后一个同学到达车站的时间 cnt[t]++; // t时刻引发的到达车站的人数,可能是多人 sum[t] += t; // t时刻引发的等待时间和,可能是多人 } // 注意,这里应计算到最后一同学可能等到的时间 for (int i = 1; i < T + m; i++) { cnt[i] += cnt[i - 1]; // 求人数的前缀和 sum[i] += sum[i - 1]; // 求时间的前缀和 } for (int i = 1; i < T + m; i++) { f[i] = i * cnt[i] - sum[i]; // 特殊处理i using namespace std; const int INF = 0x3f3f3f3f; const int N = 4000010; int ans = INF; int cnt[N], sum[N], f[N]; int n, m; // 等车人数和摆渡车往返一趟的时间 int T; // T表示最后一个同学到达车站的时间 int main() { cin >> n >> m; for (int i = 1; i <= n; i++) { int t; cin >> t; T = max(T, t); cnt[t]++; sum[t] += t; } // 注意,这里应计算到最后一同学可能等到的时间 T + m - 1 for (int i = 1; i < T + m; i++) { cnt[i] += cnt[i - 1]; // 求人数的前缀和 sum[i] += sum[i - 1]; // 求时间的前缀和 } for (int i = 1; i < T + m; i++) { // 可以多通过6个测试点 if (i >= m && cnt[i] == cnt[i - m]) { f[i] = f[i - m]; continue; } f[i] = i * cnt[i] - sum[i]; // 特殊处理i