交换论证:大臣按左右手数字之积 a_i×b_i 从小到大排队,最大奖赏最小;乘积用 Python 大整数顺扫一遍统计。
OJ: roj
题目 ID: 3599
难度:提高
标签:贪心排序数学
创建: 2026-10-02 09:53
更新: 2026-10-10 11:30
形式化题目
国王写下一对整数
即排在他前面的所有人(含国王)左手数字的乘积除以他自己右手数字,向下取整。求一个排列,使获得金币最多的大臣所得最少,输出这个最小值。
样例:国王
| 排列 | 各大臣金币 | 最大值 |
|---|---|---|
| 2 | ||
| 4 | ||
| 9 |
同一个样例换个排列最大值就从
暴力解法
思路
代码
暴力思路略(前 40% 的全排列枚举按上述思路用 itertools.permutations 即可,无独立教学价值,也不参与对拍,故不保留代码文件)。
复杂度
每个排列需要
瓶颈
瓶颈在于把
正解
思路
先做一步关键观察:设某相邻两人的左手数、右手数分别为
| 位置 | 左手乘积 | 除以的右手数 | 金币数 |
|---|---|---|---|
我们要证:当
由
于是把所有大臣按
最后扫一遍排序结果:维护一个累乘变量(初始为国王左手数
代码
/**
* 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-10 11:30
* update_at: 2026-10-10 11:30
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int BASE = 1000000000;
struct BigInt {
vector<int> d;
int sign;
BigInt(ll x = 0) { sign = 1; if (x < 0) { sign = -1; x = -x; } if (x == 0) sign = 0; while (x) { d.push_back(x % BASE); x /= BASE; } }
};
void trim(BigInt &a) { while (!a.d.empty() && a.d.back() == 0) a.d.pop_back(); if (a.d.empty()) a.sign = 0; }
BigInt read_big(string s) { BigInt a; a.d.clear(); a.sign = 1; int pos = 0; if (s[0] == '-') { a.sign = -1; pos = 1; } for (int i = s.size(); i > pos; i -= 9) { int l = max(pos, i - 9); int x = 0; for (int j = l; j < i; j++) x = x * 10 + s[j] - '0'; a.d.push_back(x); } trim(a); return a; }
int cmp_abs(const BigInt &a, const BigInt &b) { if (a.d.size() != b.d.size()) return a.d.size() < b.d.size() ? -1 : 1; for (int i = (int)a.d.size() - 1; i >= 0; i--) if (a.d[i] != b.d[i]) return a.d[i] < b.d[i] ? -1 : 1; return 0; }
BigInt add_abs(const BigInt &a, const BigInt &b) { BigInt c; c.sign = 1; int n = max(a.d.size(), b.d.size()); c.d.assign(n, 0); ll carry = 0; for (int i = 0; i < n; i++) { ll v = carry; if (i < (int)a.d.size()) v += a.d[i]; if (i < (int)b.d.size()) v += b.d[i]; c.d[i] = v % BASE; carry = v / BASE; } if (carry) c.d.push_back(carry); trim(c); return c; }
BigInt sub_abs(const BigInt &a, const BigInt &b) { BigInt c; c.sign = 1; c.d.assign(a.d.size(), 0); ll borrow = 0; for (int i = 0; i < (int)a.d.size(); i++) { ll v = (ll)a.d[i] - borrow - (i < (int)b.d.size() ? b.d[i] : 0); if (v < 0) { v += BASE; borrow = 1; } else borrow = 0; c.d[i] = v; } trim(c); return c; }
BigInt add_big(BigInt a, BigInt b) { if (a.sign == 0) return b; if (b.sign == 0) return a; if (a.sign == b.sign) { BigInt c = add_abs(a, b); c.sign = a.sign; return c; } int cmp = cmp_abs(a, b); if (cmp == 0) return BigInt(0); if (cmp > 0) { BigInt c = sub_abs(a, b); c.sign = a.sign; return c; } BigInt c = sub_abs(b, a); c.sign = b.sign; return c; }
BigInt mul_int(BigInt a, ll m) { if (a.sign == 0 || m == 0) return BigInt(0); if (m < 0) { a.sign = -a.sign; m = -m; } BigInt c; c.sign = a.sign; c.d.assign(a.d.size(), 0); ll carry = 0; for (int i = 0; i < (int)a.d.size(); i++) { __int128 v = (__int128)a.d[i] * m + carry; c.d[i] = (ll)(v % BASE); carry = (ll)(v / BASE); } while (carry) { c.d.push_back(carry % BASE); carry /= BASE; } trim(c); return c; }
BigInt div_int(BigInt a, ll m) { BigInt c; c.sign = a.sign; c.d.assign(a.d.size(), 0); ll rem = 0; for (int i = (int)a.d.size() - 1; i >= 0; i--) { __int128 cur = (__int128)rem * BASE + a.d[i]; c.d[i] = (ll)(cur / m); rem = (ll)(cur % m); } trim(c); return c; }
bool less_big(const BigInt &a, const BigInt &b) { if (a.sign != b.sign) return a.sign < b.sign; if (a.sign == 0) return false; int cmp = cmp_abs(a, b); return a.sign > 0 ? cmp < 0 : cmp > 0; }
bool is_zero(const BigInt &a) { return a.sign == 0; }
ostream& operator<<(ostream &os, const BigInt &a) { if (a.sign == 0) { os << 0; return os; } if (a.sign < 0) os << '-'; int n = a.d.size(); os << a.d[n - 1]; for (int i = n - 2; i >= 0; i--) os << setw(9) << setfill('0') << a.d[i]; return os; }
struct Person{ll a,b;};
bool cmp_person(const Person &x, const Person &y){ return x.a * x.b < y.a * y.b; }
int main(){ ios::sync_with_stdio(false); cin.tie(nullptr); int n; if(!(cin>>n)) return 0; string sa,sb; cin>>sa>>sb; BigInt prod=read_big(sa), best(0); vector<Person> p(n); for(int i=0;i<n;i++) cin>>p[i].a>>p[i].b; sort(p.begin(),p.end(),cmp_person); for(int i=0;i<n;i++){ BigInt cur=div_int(prod,p[i].b); if(less_big(best,cur)) best=cur; prod=mul_int(prod,p[i].a); } cout<<best<<'\n'; return 0; }#!/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-07-05 21:47
# update_at: 2026-07-05 21:47
import sys
def solve() -> None:
it = iter(sys.stdin.buffer.read().split())
n = int(next(it))
a, b = int(next(it)), int(next(it)) # 国王的左右手数字
# 大臣按 a_i * b_i 从小到大排:交换相邻两人不会让最大奖赏变大
ministers = sorted(
((int(next(it)), int(next(it))) for _ in range(n)),
key=lambda p: p[0] * p[1],
)
best = 0
for ai, bi in ministers:
best = max(best, a // bi) # 队列里排在他前面的人左手的乘积
a *= ai
print(best)
if __name__ == "__main__":
solve()复杂度
排序
总结
- 核心是邻项交换排序(交换论证):只比较相邻两人,推出"
小的站前面"这一全局排序依据,与"区间调度按右端点排序"是同一套方法论。 - 影响某人金币数的只有他前面的左手乘积和他自己的右手数,交换相邻两人不影响其他人,这是整个论证成立的支点。
- 用 Python 做这类高精度贪心题非常划算:大整数乘除开箱即用,代码只剩排序加一次线性扫描。