0题目原文(本地存了一份)
原题在洛谷上(页头有链接)。别人的网站不归我们管,打不开、改版、题号调整都可能发生。 所以每个解析页都把题面转录一份存在本地,跟着仓库一起进版本库。
转录自洛谷 P5365,日期见页头。两边不一致时信原站。
题目描述
正在上大学的小皮球热爱英雄联盟这款游戏,而且打的很菜,被网友们戏称为「小学生」。
现在,小皮球终于受不了网友们的嘲讽,决定变强了,他变强的方法就是:买皮肤!
小皮球只会玩 N 个英雄,因此,他也只准备给这 N 个英雄买皮肤,并且决定,以后只玩有皮肤的英雄。
这 N 个英雄中,第 i 个英雄有 Kᵢ 款皮肤,价格是每款 Cᵢ Q 币(同一个英雄的皮肤价格相同)。
为了让自己看起来高大上一些,小皮球决定给同学们展示一下自己的皮肤,展示的思路是这样的: 对于有皮肤的每一个英雄,随便选一个皮肤给同学看。
比如,小皮球共有 5 个英雄,这 5 个英雄分别有 0, 0, 3, 2, 4 款皮肤,
那么,小皮球就有 3 × 2 × 4 = 24 种展示的策略。
现在,小皮球希望自己的展示策略能够至少达到 M 种,请问,小皮球至少要花多少钱呢?
输入格式
第一行,两个整数 N, M。第二行,N 个整数 Kᵢ。第三行,N 个整数 Cᵢ。
输出格式
一个整数,表示小皮球达到目标最少的花费。
说明/提示
样例解释:每一个英雄都只有 4 款皮肤,每款皮肤 2 Q 币,那么每个英雄买 3 款,
3 × 3 × 3 ≥ 24,共花费 6 × 3 Q 币。
数据范围:共 10 组数据,第 i 组数据满足 N ≤ max(5, log₂⁴ i)。
100% 的数据:M ≤ 10¹⁷,1 ≤ Kᵢ ≤ 10,1 ≤ Cᵢ ≤ 199。保证有解。
输入输出样例
输入
3 24 4 4 4 2 2 2
输出
18
三个英雄各有 4 款皮肤、每款 2 Q 币,要凑够 24 种策略 ⇒ 每人买 3 款(3×3×3 = 27 ≥ 24),
花 9 × 2 = 18。
⚠⚠ 这一组样例把本页三个错法全放过了(都输出 18)—— 和同章的 P1776(四个错法全挡住)正好是两个极端。
1★ 关键的一步:花费当容量,策略数当价值
这道题的门槛在看出它是个背包。翻译一遍:
每个英雄 = 一个「组」,组内的选项是「买 0 / 1 / 2 / … / K[i] 款」
选一个选项要花 k × C[i] <- 容量
选完之后策略数乘上 k <- 价值(乘法,不是加法)
问:策略数 ≥ M 时,容量最少是多少
⇒ 分组背包(每组最多选一个选项),只是「价值」的合并方式从加法换成了乘法。
// P5365 [SNOI2017] 英雄联盟 —— ★ 这一版就能 AC//// 题意:N 个英雄,第 i 个有 K[i] 款皮肤、每款 C[i] Q 币。// 给第 i 个英雄买 k 款,展示策略数就乘上 k(买 0 款的英雄不参与展示)。// 要让**策略数 ≥ M**,问最少花多少钱。//// ★ 关键的一步(一):**把「花费」当容量、「策略数」当价值** ——// 这是一个**分组背包**:每个英雄是一组,组内选「买几款」(0 .. K[i]),最多选一个。// f[j] = 花不超过 j 元时,能达到的**最大策略数**// f[j] = max(f[j], f[j − k·C[i]] × k) k = 2 .. K[i]// 答案 = 最小的 j 使 f[j] ≥ M。//// ★ 关键的一步(二):**k = 1 永远不用考虑** —— 买 1 款和买 0 款对乘积都是 ×1,// 而买 1 款要多花 C[i]。⇒ 内层从 k = 2 起。// (反过来,**强制每个英雄至少买 1 款**是一个真实的错法,见解析页第 ③ 步。)//// ★★★ 关键的一步(三):**乘积必须截断**。// N 顶格 121、每个英雄 10 款 ⇒ 策略数最大 10^121,`long long` 装不下十分之一。// 而我们只关心「有没有到 M」⇒ 把每次乘出来的值**和 M 取 min** 就行。// ⇒ 不截断会溢出成负数,答案当场错(解析页第 ④ 步)。//// ⚠ 容量上界是算出来的:Σ K[i]·C[i] ≤ 121 × 10 × 199 = 240 790。// ⚠ M ≤ 10^17 ⇒ 读 M 必须用 long long。
#include <bits/stdc++.h>using namespace std;typedef long long ll;
int main() { int n; ll M; if (!(cin >> n >> M)) return 0; vector<int> K(n), C(n); for (int i = 0; i < n; i++) cin >> K[i]; for (int i = 0; i < n; i++) cin >> C[i];
int cap = 0; for (int i = 0; i < n; i++) cap += K[i] * C[i]; // ★ 容量上界:全买满
vector<ll> f(cap + 1, 1); // 一款都不买 ⇒ 策略数 1 for (int i = 0; i < n; i++) for (int j = cap; j >= 0; j--) // 倒序:这一组只选一次 for (int k = 2; k <= K[i]; k++) { // ★ k = 1 和 k = 0 等价,跳过 int cost = k * C[i]; if (cost > j) break; ll v = f[j - cost] * k; if (v > M) v = M; // ★★★ 截断,否则 10^121 会溢出 f[j] = max(f[j], v); }
for (int j = 0; j <= cap; j++) if (f[j] >= M) { cout << j << "\n"; return 0; } cout << cap << "\n"; // 题面保证有解,走不到这儿 return 0;}点「运行 ▶」看结果
题面的样例解释里悄悄给了一个关键信息:「5 个英雄分别有 0, 0, 3, 2, 4 款皮肤,
那么共有 3 × 2 × 4 = 24 种策略」——
买 0 款的英雄不参与展示,也就是对乘积贡献 ×1。
而买 1 款呢?那个英雄只有一款皮肤可展示 ⇒ 也是 ×1,可它多花了 C[i]。
⇒ k = 1 严格劣于 k = 0,内层从 k = 2 起就行。
⚠ 反过来说,「强制每个英雄至少买 1 款」是一个真实的错法(第 ③ 步)。
2★★ 容量上界和复杂度:两笔都要自己乘一遍
容量上界 = Σ Kᵢ · Cᵢ ≤ 121 × 10 × 199 = 240 790 <- f 数组开这么大
复杂度 = O(容量 × Σ Kᵢ) ≈ 2.4 × 10⁵ × 1210 ≈ 3 × 10⁸
(N 的上界来自题面那句 N ≤ max(5, log₂⁴ i),i ≤ 10 ⇒ log₂(10)⁴ ≈ 121.6。)
顶格实测(N=121,K=10,C=199,M=10¹⁷) |
|
|---|---|
容量上界 ΣK·C |
240 790 |
f 数组(long long) |
1881 KB |
| 内层执行次数 | 260 921 133 |
| 本机毫秒 | 233(时限 2000 ⇒ 余量约 8.6 倍) |
| 答案 | 21 293 |
3★★ 两个真错法(而官方样例一个都没挡住)
每档 300 轮:
| 档位 | N≤5, K≤4, C≤10 |
N≤6, K≤10, C≤199 |
N≤8, K≤10, C≤50 |
N≤4, K≤10, C≤199 |
|---|---|---|---|---|
| 外层正序被抓 | 157 | 240 | ★ 251 | 198 |
| 强制买 1 款被抓 | ★ 216 | 123 | 140 | 122 |
「外层正序」要英雄多、单价差别大才现形(同一个英雄被反复买才划算); 「强制买 1 款」要英雄多而 K 小才现形(那时候「一款都不买」的英雄才多)。 ⇒ 这已经是这两轮里第三次看到「同一个旋钮把两个 bug 推向相反方向」 (P1049、P2925)。 ⇒ 一个生成器上的旋钮,至少要往两头各拧一次再下结论。
4★★★ 而「必须截断」这一条 —— 是我的草稿,被实测打回来了
草稿里我写的是:
N顶格 121、每个英雄 10 款 ⇒ 策略数最大10¹²¹,long long只到9.2 × 10¹⁸。 不截断必溢出、必错。
| 随机 2000 组:截断版 vs 不截断版 | ★ 不一致 0 组 |
顶格 N=121, K=10, C=199,M 取 10¹⁷ / 10¹⁸ / 3×10¹⁸ / 9×10¹⁸ |
答案 21293 / 22686 / 23283 / 23880,★ 四档全部相同 |
设答案下标是 J(最小的、满足 f[J] ≥ M 的那个 j)。
- 所有
j < J的格子,按J的定义都有f[j] < M; - 而
f[J]只可能由某个f[J − cost](下标更小)乘上k ≤ 10得来 ⇒ 被用到的中间值 < 10 M。
题面 M ≤ 10¹⁷ ⇒ 用得上的值 < 10¹⁸ < 2⁶³ —— 连有符号 long long 都够。
(下标 > J 的格子确实会溢出,但从 0 往上扫时根本走不到那儿。)
⇒ 这和 P1164 那条 是同一个形状的论证: 「答案 ≥ 任何一个被用到的中间值」⇒ 题面给的答案上界,就是「类型够不够」的充分条件。
★★ 但结论不是「所以别写截断」:
截断让这份代码不依赖上面那段推理 —— 换一道 M 更大、或者 K 更大的题,
上面那个 10 M 的余量(9.2 倍)说没就没。
⇒ 该写还是写,但要知道自己写它是为了「不用去想」,而不是「不写就错」。
5★ 第三个错法:M 用 int 读 —— 它连读入都过不去
题面写着 M ≤ 10¹⁷,而 int 只到 2.1 × 10⁹ —— 差 8 个数量级。
⚠ 而它的失败方式比「算错」更彻底:cin >> int 读到超范围的数会置错误位,
于是那句 if (!(cin >> n >> M)) return 0; 直接返回 —— 程序一个字都不输出。
★ 触发条件是一条数值线:M > 2³¹ − 1。
⇒ 小数据上它和正解一模一样(本页默认档 M ≤ 10⁶,300 轮精确的 0),
而题面顶格必挂。
⇒ 又一次「溢出的触发条件是一条数值线,生成器够不够是算术题」。
6度量程序和生成器
7一页纸
| 关键的一步 | 花费当容量、策略数当价值的分组背包(价值用乘法合并) |
| 哪一版能 AC | p5365.cpp —— 顶格 2.6 亿次 / 233 毫秒(时限 2000) |
| ★★ 一个白捡的剪枝 | k = 1 永远不用考虑(对乘积和 k = 0 一样,却要多花 C[i]) |
| 两笔算术 | 容量上界 ΣK·C = 240 790;复杂度 O(容量 × ΣK) ≈ 3 × 10⁸ |
| 错法一 | 分组背包外层正序 ⇒ 同一个英雄被买好几轮;被抓 157~251 |
| 错法二 | 强制每个英雄至少买 1 款(样例解释里的 0, 0 就是反例);被抓 122~216 |
| 错法三 | M 用 int ⇒ 读入直接失败、一个字都不输出;触发线 M > 2³¹−1 |
| ⚠⚠ 被打回的草稿 | 「不截断必溢出」是错的:随机 2000 组 + 顶格四档 M 全部相同 |
| 它凭什么对 | 答案下标之前的 f 都 < M ⇒ 被用到的中间值 < 10M < 2⁶³(和 P1164 同形状) |
| ★ 但截断还是要写 | 它让代码不依赖那段推理 —— 那个余量只有 9.2 倍 |
| ⚠ 官方样例 | ★ 三个错法一个都没挡住(和同章 P1776 正好两个极端) |