接水时间短的人排前面即最优:交换论证排除相邻逆序对,按时间升序排序后总等待为 Σ(n-1-pos)·t,除以 n 输出两位小数。
OJ: roj
题目 ID: 1319
难度:入门
标签:入门贪心排序python
创建: 2026-09-30 04:53
更新: 2026-10-05 09:18
题目描述
输入第一行 10 / 56 12 1 99 1000 234 33 55 99 812,输出 3 2 7 8 1 4 9 6 10 5 与 291.90。
思路
按接水时间升序排序即可得最优;交换论证说明任何相邻逆序对交换都让总等待严格减少,所以最优解就是升序。排好后第 pos 位(从 0 起)的权为 n-1-pos,总等待=Σ (n-1-pos)·t[pos],除以 n 即平均等待。
参考代码
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 05:10
# update_at: 2026-09-30 05:10
import sys
THRESHOLD = 16 # 插入排序阈值:段长 ≤16 直接插入排序(libstdc++ 的 _S_threshold)
def lt(x: tuple[int, int], y: tuple[int, int]) -> bool:
"""比较两条记录:只比接水时间;编号只是输出标签,不参与比较。"""
return x[0] < y[0]
def move_median_to_first(a: list[tuple[int, int]], res: int, i: int, j: int, k: int) -> None:
"""三数取中:把 a[i]、a[j]、a[k] 的中位数换到 res 位置(快速排序选基准)。"""
if lt(a[i], a[j]):
if lt(a[j], a[k]):
a[res], a[j] = a[j], a[res]
elif lt(a[i], a[k]):
a[res], a[k] = a[k], a[res]
else:
a[res], a[i] = a[i], a[res]
elif lt(a[i], a[k]):
a[res], a[i] = a[i], a[res]
elif lt(a[j], a[k]):
a[res], a[k] = a[k], a[res]
else:
a[res], a[j] = a[j], a[res]
def unguarded_partition(a: list[tuple[int, int]], first: int, last: int, pivot: int) -> int:
"""Hoare 划分:小于基准的挪到左边、大于等于的挪到右边,返回分界点。"""
while True:
while lt(a[first], a[pivot]): # 左指针停在第一个 ≥ 基准处
first += 1
last -= 1
while lt(a[pivot], a[last]): # 右指针停在第一个 ≤ 基准处
last -= 1
if first >= last:
return first
a[first], a[last] = a[last], a[first]
first += 1
def partition_pivot(a: list[tuple[int, int]], first: int, last: int) -> int:
"""三分取中选基准(基准换到段首),再对 [first+1, last) 划分,返回分界点。"""
mid = first + (last - first) // 2
move_median_to_first(a, first, first + 1, mid, last - 1)
return unguarded_partition(a, first + 1, last, first)
def push_heap(a: list[tuple[int, int]], base: int, hole: int, top: int, value: tuple[int, int]) -> None:
"""大顶堆上插:value 从洞 hole 沿父链上浮到合适位置。"""
parent = (hole - 1) // 2 if hole else 0 # C++ 中 (-1)/2 截断为 0,Python 需手动对齐
while hole > top and lt(a[base + parent], value):
a[base + hole] = a[base + parent]
hole = parent
parent = (hole - 1) // 2 if hole else 0
a[base + hole] = value
def adjust_heap(a: list[tuple[int, int]], base: int, hole: int, length: int, value: tuple[int, int]) -> None:
"""大顶堆下沉:value 从洞 hole 逐层下沉,最后经上浮收尾。"""
top = hole
child = hole
while child < (length - 1) // 2:
child = 2 * (child + 1)
if lt(a[base + child], a[base + child - 1]): # 两个孩子里较小的先上位
child -= 1
a[base + hole] = a[base + child]
hole = child
if length % 2 == 0 and child == (length - 2) // 2: # 偶数长时右孩子是最后一个
child = 2 * (child + 1)
a[base + hole] = a[base + child - 1]
hole = child - 1
push_heap(a, base, hole, top, value)
def heapsort(a: list[tuple[int, int]], first: int, last: int) -> None:
"""划分深度耗尽时的兜底:整段堆排序,保证最坏 O(n log n)。"""
length = last - first
if length < 2:
return
parent = (length - 2) // 2
while True: # 自底向上建大顶堆
value = a[first + parent]
adjust_heap(a, first, parent, length, value)
if parent == 0:
break
parent -= 1
end = last
while end - first > 1: # 堆顶(最大值)依次换到段尾
end -= 1
value = a[end]
a[end] = a[first]
adjust_heap(a, first, 0, end - first, value)
def introsort_loop(a: list[tuple[int, int]], first: int, last: int, depth: int) -> None:
"""introsort 主循环:段长超过阈值就划分;深度耗尽改走堆排序。"""
while last - first > THRESHOLD:
if depth == 0:
heapsort(a, first, last)
return
depth -= 1
cut = partition_pivot(a, first, last)
introsort_loop(a, cut, last, depth) # 右半段递归
last = cut # 左半段转成循环,避免深递归
def unguarded_linear_insert(a: list[tuple[int, int]], i: int) -> None:
"""把 a[i] 插入左侧已排序前缀(段首 ≤ a[i],正常不会越过下标 0)。"""
value = a[i]
j = i - 1
while j >= 0 and lt(value, a[j]): # j >= 0 只是防 Python 负下标回绕的兜底
a[i] = a[j]
i, j = j, j - 1
a[i] = value
def insertion_sort(a: list[tuple[int, int]], first: int, last: int) -> None:
"""插入排序:introsort 对小段的收尾方式。"""
if first == last:
return
for i in range(first + 1, last):
if lt(a[i], a[first]):
value = a[i] # 比段首还小:整段右移一格,插到最前
for j in range(i, first, -1):
a[j] = a[j - 1]
a[first] = value
else:
unguarded_linear_insert(a, i)
def final_insertion_sort(a: list[tuple[int, int]], first: int, last: int) -> None:
"""introsort 收尾:前 16 个用带边界保护的插入排序,其余用无保护版本。"""
if last - first > THRESHOLD:
insertion_sort(a, first, first + THRESHOLD)
for i in range(first + THRESHOLD, last):
unguarded_linear_insert(a, i)
else:
insertion_sort(a, first, last)
def std_sort(a: list[tuple[int, int]]) -> None:
"""按 C++ std::sort(libstdc++ introsort)的步骤排序,并列时间的顺序与参考输出一致。"""
if a:
n = len(a)
introsort_loop(a, 0, n, 2 * (n.bit_length() - 1)) # 深度上限 = 2*floor(log2 n)
final_insertion_sort(a, 0, n)
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
n = next(data)
# 参考程序的接水时间数组是全局的:读不满 n 个时缺项为 0
times = [next(data, 0) for _ in range(n)]
order = [(t, i) for i, t in enumerate(times, 1)]
std_sort(order) # 贪心:接水时间短的排前面,平均等待时间最小
# 第 pos 位要等前面所有人接完:总等待 = Σ (n-1-pos) * t,平均 = 总等待 / n
total = sum(t * (n - 1 - pos) for pos, (t, _) in enumerate(order))
ids = ' '.join(str(i) for _, i in order)
print(f'{ids}\n{total / n:.2f}')
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 09:18
* update_at: 2026-10-05 09:18
*/
// main.cpp:排队接水。
// 贪心:按接水时间升序排序;总等待 = Σ (n-1-pos)*t[pos],平均 = 总等待 / n。
#include <iostream>
#include <vector>
#include <algorithm>
#include <iomanip>
using namespace std;
typedef long long ll;
const int MAXN = 1005;
int n;
int t[MAXN]; // 第 i 个人的接水时间(缺项补 0)
void read_input() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> t[i];
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
read_input();
// 把 (时间, 编号) 一起排序,std::sort 非稳定,输出顺序与参考答案一致。
vector<pair<int,int>> a;
a.reserve(n);
for (int i = 1; i <= n; i++) a.emplace_back(t[i], i);
sort(a.begin(), a.end());
// 第 pos 位(从 0 计)的等待权为 n-1-pos,求加权和再除以 n。
ll total = 0;
for (int pos = 0; pos < n; pos++) total += (ll)a[pos].first * (n - 1 - pos);
cout.setf(ios::fixed); cout << setprecision(2);
for (int i = 0; i < n; i++) {
if (i) cout << ' ';
cout << a[i].second;
}
cout << "\n" << (double)total / n << "\n";
return 0;
}