Crossing River

GitHub跳转原题关系图返回列表

排序后按最慢者递推:最快者往返快递一人,或两名最快者摆渡两名最慢者,逐个取较小值。

OJ: roj

题目 ID: 1232

难度:普及-

标签:贪心动态规划排序python

创建: 2026-09-30 00:56

更新: 2026-10-05 06:04

题目描述

t 组数据,每组 n 个人要过河:船每次最多载 2 人,且至少 1 人把船划回,用时由船上较慢者决定,求把所有人送到对岸的最少总时间。输入第一行 t,之后每组第一行为 n、第二行为 n 个过河时间;输出每组一行答案。数据范围 t⩽10t \leqslant 10,n⩽1000n \leqslant 1000,ai⩽100a_i \leqslant 100。样例:输入 1 / 4 / 1 2 5 10,输出 17。

思路

先排序,让当前最慢者永远是末尾;每轮只送走最慢者,要么最快者往返快递他一人(代价 a1+aia_1+a_i),要么两名最快者摆渡两名最慢者(代价 a1+2a2+aia_1+2a_2+a_i),两者取较小值逐层递推。

参考代码

Python 版:

python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-09-30 00:56
# update_at: 2026-09-30 00:56

import sys
from collections.abc import Iterator


def cross(times: list[int]) -> int:
    """返回 times 中所有人过河的最短总时间(已升序)。

    dp[i] = 送走最慢的 i+1 个人所需时间。送走最慢者 a[i] 只有两种不交叉的运输剧本,
    每轮都让 a[0] 或 a[1] 划船回来,故 dp[i] = min(dp[i-1] + a[0] + a[i],
    dp[i-2] + a[0] + 2*a[1] + a[i])。原地滚动两个变量即可,不必开数组。
    """
    dp_prev2: int = times[0]          # dp[0]:只剩一人时他自己过河
    if len(times) == 1:
        return dp_prev2
    dp_prev1: int = times[1]          # dp[1]:两人一起过河,慢者定速

    for i in range(2, len(times)):
        # best 方案:最快者来回送最慢者,一次只消耗掉一个最慢者
        solo = dp_prev1 + times[0] + times[i]
        # duo 方案:最快两人先过、次快者带船回、两慢者同过、最快者带船回
        duo = dp_prev2 + times[0] + 2 * times[1] + times[i]
        dp_prev2, dp_prev1 = dp_prev1, min(solo, duo)  # 滚动:省掉长度 n 的数组

    return dp_prev1


def solve() -> None:
    """逐组读入 n 与 n 个过河时间,输出每组的最短总时间。"""
    data: Iterator[int] = iter(map(int, sys.stdin.buffer.read().split()))
    out: list[str] = []
    for _ in range(next(data)):       # 组数 t,每组输出一行
        n = next(data)
        times = sorted(next(data) for _ in range(n))  # 只有有序才能按"最慢者"递推
        out.append(str(cross(times)))
    print('\n'.join(out))


if __name__ == "__main__":
    solve()

C++ 版(同一算法):

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-10-05 06:04
 * update_at: 2026-10-05 06:04
 */
#include <iostream>
#include <algorithm>
using namespace std;

typedef long long ll;

const int MAXN = 1005;
ll a[MAXN]; // a[i] 表示第 i 个人单独划船过河所需的时间,已按升序排列

// 求把 a[0..n-1] 全部送到对岸的最少总时间。
// 每次只送走当前最慢的人,有两种剧本:最快者往返快递一人,或两名最快者摆渡两名最慢者。
ll crossing_river(int n) {
    if (n == 1) {
        return a[0]; // 只剩一人,他自己划过去
    }
    ll dp_prev2 = a[0]; // dp[0]:只有一个人的花费
    ll dp_prev1 = a[1]; // dp[1]:两人一起过去,耗时由慢者决定
    for (int i = 2; i < n; i++) {
        // 方案 A:最快者送最慢者过去,自己再划回来,本轮只送走一人
        ll solo = dp_prev1 + a[0] + a[i];
        // 方案 B:两名最快者先把船送到对岸,两名最慢者同船过去,本轮送走两人
        ll duo = dp_prev2 + a[0] + 2 * a[1] + a[i];
        ll best = min(solo, duo);
        dp_prev2 = dp_prev1;
        dp_prev1 = best;
    }
    return dp_prev1; // 即 dp[n-1]
}

int main() {
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        for (int i = 0; i < n; i++) {
            cin >> a[i];
        }
        // 先排序:只有有序,"当前最慢的人"才是末尾那个人
        sort(a, a + n);
        cout << crossing_river(n) << "\n";
    }
    return 0;
}