You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

6.7 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

AcWing 475. 摆渡车

一、题目描述

n名同学要乘坐摆渡车从 人大附中 前往 人民大学,第 i 位同学在第 t_i分钟去等车。只有一辆摆渡车在工作,但摆渡车容量可以视为无限大。摆渡车从人大附中出发, 把车上的同学送到人民大学,再回到人大附中(去接其他同学),这样往返一趟总共花费m分钟(同学上下车时间忽略不计)。摆渡车要将所有同学都送到人民大学。

凯凯很好奇,如果他能任意安排摆渡车出发的时间,那么这些同学的 等车时间之和最小 为多少呢?

注意:摆渡车回到人大附中后可以即刻出发。

输入格式 第一行包含两个正整数 n,m,以一个空格分开,分别代表等车人数和摆渡车往返一趟的时间。

第二行包含 n 个正整数,相邻两数之间以一个空格分隔,第 i 个非负整数 t_i代表第i个同学到达车站的时刻。

输出格式 输出一行,一个整数,表示所有同学等车时间之和的最小值(单位:分钟)。

输入样例

5 5
11 13 1 5 5

输出样例

4

二、解题思路

如果把时间看作一个数轴,那么所有同学到达车站的时刻就是数轴上的点,安排车辆的工作就是把数轴划分成若干个 左开右闭 的子区间,如下图所示:

例如测试样例中摆渡车安排在时刻1、6、13时,所有同学的等待时间之和最少,为4。一共安排了3次乘车,将数轴划分3段左开右闭的区间,分别是:(0,1],(1,6],(6,13],每段长度都≥m,上图中m=5

这样,等待时间之和就转换成了所有点到各自所属区间右边界的距离之和。

状态表示

f[i]表示数轴上对于前i个点,且最后一段的右边界为i,位于(0,i]的所有点到各自所属区间右边界的 距离之和的最小值

状态转移

设最后一段是(j,i],而每段长度ij≥m,则有j≤im。如果第k位同学到达时间t[k]属于区间(j,i],即j<t[k]≤i,他到右边界i的距离是it[k]。那么累加所有属于区间(j,i]的点到i点距离之和,然后加上上一阶段的状态f[j],就得到了状态转移方程:

\large f[i]=\min_{j≤im}(\sum_{j<t[k]≤i}(it[k])+f[j])

进一步思考

\sum_{j<t[k]≤i}(it[k])=(\sum_{j<t[k]≤i}{i})(\sum_{j<t[k]≤i}{t[k]})

其中\displaystyle \sum{i}\displaystyle \sum{t[k]} 都可以通过前缀和 计算出来。

\displaystyle \sum{i}=(cnt[i]cnt[j])×icnt[i]表示区间(0,i]中同学的个数。 \displaystyle \sum{t[k]}=sum[i]sum[j]sum[i]表示区间(0,i]中所有同学到达的时间之和。

状态转移方程可以写成:

\large f[i]=\min_{j≤im}(cnt[i]cnt[j])×i(sum[i]sum[j])+f[j]

时间复杂度

f[i]表示的是在时间轴上对于前i个点的最优解,i≤4×10^6,所以时间复杂度为O(10^{12})

代码实现

#include <bits/stdc++.h>
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<m的情况
        for (int j = 0; j <= i - m; j++)
            f[i] = min(f[i], (cnt[i] - cnt[j]) * i - (sum[i] - sum[j]) + f[j]);
    }
    // 取右边界取的漂亮
    int ans = INF;
    for (int i = T; i < T + m; i++) ans = min(ans, f[i]);
    cout << ans << endl;
    return 0;
}

三、算法优化

DP优化常用的两种方法:

  • 剪去无用转移 考虑区间(j,i],在计算f[i]时,j0开始枚举。其实可以缩小j的范围,从i - 2 * m开始枚举。因为当区间的长度≥2m时,可以安排摆渡车跑一个来回,等待时间不会变长。通过此性质,可剪去大量无用转移,状态转移方程更新为:
\large f[i]=\min_{i2m<j≤im}(cnt[i]cnt[j])×i(sum[i]sum[j])+f[j]
  • 剪去无用状态 对于某个阶段的状态f[i],如果在区间(im,i]中没有任何点,那么状态f[i]=f[i-m]。因为区间中没有任何学生,可以不安排摆渡车,不会增加学生的等待时间。

优化后时间复杂度O(nm^2+t)

代码实现

#include <bits/stdc++.h>
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<m的情况

        // 可以多通过4个测试点
        for (int j = max(0, i - 2 * m + 1); j <= i - m; j++)
            f[i] = min(f[i], (cnt[i] - cnt[j]) * i - (sum[i] - sum[j]) + f[j]);
    }

    // 注意,最后一名同学上车的时刻在[TT + m),求 其中最小值。
    for (int i = T; i < T + m; i++) ans = min(ans, f[i]);
    cout << ans << endl;
    return 0;
}