史莱姆鱼

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

相邻交换算出差值 2(t_i c_j − t_j c_i),按比值 t_i/c_i 升序排序后一次线性扫描即得最少体力。

OJ: roj

题目 ID: 20005

难度:普及-

标签:贪心排序python

创建: 2026-10-02 19:28

更新: 2026-10-07 13:50

题目描述

阿什尼要运 nn 条史莱姆鱼回饭店:运第 ii 条鱼需要 2ti2t_i 个单位时间(往返),鱼会随时间变重——在时刻 TT 开始运第 ii 条鱼要消耗 T⋅ciT\cdot c_i 的体力。第一条鱼在时刻 0 开始运送,体力为 0。选择合适的运送顺序,求最少消耗的总体力。

输入:第一行一个整数 nn;接下来 nn 行,每行两个整数 ti,cit_i, c_i。输出:一个整数,表示最少消耗的体力。数据范围:n⩽105n \leqslant 10^5,ti⩽2×106t_i \leqslant 2\times 10^6,ci⩽100c_i \leqslant 100。

样例输入 6\n3 1 2 5 2 3 3 2 4 1 1 6,样例输出 86。

思路

设相邻两条鱼 ii(先)、jj(后),它们之前的鱼共耗时 TT,两条鱼贡献的体力相减得 (i 先)−(j 先)=2(ticj−tjci)(i\text{ 先})-(j\text{ 先})=2(t_ic_j-t_jc_i),与其余鱼无关;因此若 ticj>tjcit_ic_j>t_jc_i(即 ti/ci>tj/cjt_i/c_i>t_j/c_j)交换后更优,最优顺序就是按 ti/cit_i/c_i 升序。比较用交叉相乘 ticjt_ic_j 与 tjcit_jc_i,避免浮点误差和 ci=0c_i=0 的除零。排序后从时刻 0 顺序扫描:先累加当前开始时刻乘 cic_i,再把时刻推进 2ti2t_i,答案用 long long(最大约 4×10184\times10^{18})。

参考代码

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-10-02 19:28
# update_at: 2026-10-02 19:28

import sys
from functools import cmp_to_key


def by_ratio(a: tuple[int, int], b: tuple[int, int]) -> int:
    """按脾气比 t_i/c_i 升序比较:交叉相乘,避免除法和 c_i=0 的除零。"""
    return a[0] * b[1] - b[0] * a[1]


def solve() -> None:
    data = iter(map(int, sys.stdin.buffer.read().split()))
    n = next(data)
    fish = [(next(data), next(data)) for _ in range(n)]  # 每条鱼 (t_i, c_i)
    fish.sort(key=cmp_to_key(by_ratio))  # 相邻逆序交换只会更优,故按比值排序即最优

    elapsed = 0  # 当前时刻:前面所有鱼往返耗时之和
    cost = 0
    for take, temper in fish:
        cost += elapsed * temper  # 此刻开始运的体力 = 时刻 × 脾气(第一条体力 0)
        elapsed += take * 2       # 运一条鱼要往返,耗时 2*t_i

    print(cost)


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-06 02:17
 * update_at: 2026-10-06 02:17
 */
#include <cstdio>
#include <algorithm>

typedef long long ll;

const int MAXN = 100005;

// 每条鱼的两个参数
struct Fish {
    ll t; // 运送一条鱼需要 2*t 的时间
    ll c; // 脾气系数,开始时刻为 S 时消耗 S*c 体力
};

int n;
Fish fish[MAXN];

// 贪心比较:t_i/c_i 小的排前面,用交叉相乘避免浮点除法和除零
bool cmp_fish(const Fish &a, const Fish &b) {
    return a.t * b.c < b.t * a.c;
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%lld %lld", &fish[i].t, &fish[i].c);

    // 按 t_i/c_i 升序排序:交换相邻两条可证逆序对必劣
    std::sort(fish + 1, fish + n + 1, cmp_fish);

    // elapsed 是当前鱼的开始时刻;第一条鱼从时刻 0 开始,体力为 0
    ll elapsed = 0;
    ll ans = 0;
    for (int i = 1; i <= n; ++i) {
        ans += elapsed * fish[i].c; // 记账当前鱼的体力
        elapsed += fish[i].t * 2;   // 推进时刻:往返耗时 2*t
    }
    printf("%lld\n", ans);
    return 0;
}