0题目原文(本地存了一份)
原题在洛谷上(页头有链接)。别人的网站不归我们管,打不开、改版、题号调整都可能发生。 所以每个解析页都把题面转录一份存在本地,跟着仓库一起进版本库。
转录自洛谷 P1060,日期见页头。两边不一致时信原站。
题目描述
金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间他自己专用的很宽敞的房间。
更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,
只要不超过 N 元钱就行”。今天一早金明就开始做预算,但是他想买的东西太多了,
肯定会超过妈妈限定的 N 元。于是,他把每件物品规定了一个重要度,分为 5 等:
用整数 1−5 表示,第 5 等最重要。他还从因特网上查到了每件物品的价格(都是整数元)。
他希望在不超过 N 元(可以等于 N 元)的前提下,
使每件物品的价格与重要度的乘积的总和最大。
设第 j 件物品的价格为 vⱼ,重要度为 wⱼ,共选中了 k 件物品,
编号依次为 j₁, j₂, …, j_k,则所求的总和为:
v[j1] × w[j1] + v[j2] × w[j2] + … + v[jk] × w[jk]
请你帮助金明设计一个满足要求的购物单。
输入格式
第一行,为 2 个正整数,用一个空格隔开:n, m(n < 3 × 10⁴,m < 25)
其中 n 表示总钱数,m 为希望购买物品的个数。
从第 2 行到第 m+1 行,第 j 行给出了编号为 j−1 的物品的基本数据,
每行有 2 个非负整数 v, p(其中 v 表示该物品的价格 (v ≤ 10⁴),
p 表示该物品的重要度(1 ≤ p ≤ 5))。
输出格式
1 个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(< 10⁸)。
说明/提示
NOIP 2006 普及组 第二题
输入输出样例
输入
1000 5 800 2 400 5 300 5 400 3 200 2
输出
3900
1000 5 ⇒ 一千块钱、五件物品(⚠ 不是「五件物品、一千块钱」)。
最优解是买 400×5 + 300×5 + 200×2 = 3900,一共花 900 元。
★ 这一组样例把本页三个错法全挡住了(3900 vs 12 / 0 / 5000)—— 而这在本书里是少见的:它是「样例过滤器」那条规律的另一个极端。
1★ 关键的一步:价值 = 价格 × 重要度
这道题在题单里的作用是「确认自己是真的会了」—— 它是第 23 章
那份 fast.cpp 的原样搬运,只是把「价值」换成了一个要自己算的量:
| 题 | 体积(费用) | 价值 |
|---|---|---|
| P1048 采药 | 采一株的时间 | 草药的价值(题面直接给) |
| P1049 装箱问题 | 物品体积 | ★ 就是体积本身(题面没给价值) |
| P1060 开心的金明 | 价格 v |
★ v × p(要自己乘出来) |
⇒ 一维倒序那三行一个字都没变。背包题的「难」几乎从来不在 DP 上, 在于看出费用是什么、价值是什么。
// P1060 [NOIP 2006 普及组] 开心的金明 —— ★ 这一版就能 AC//// 题意:总钱数 n,m 件物品,第 j 件价格 v,重要度 p(1~5);// 在不超过 n 元的前提下,最大化 Σ v × p。//// ★ 这道题是纯模板:**价值 = 价格 × 重要度**,剩下的和[第 23 章](/ch/23-knapsack-01/)// 那份 fast.cpp 一个字都不差。它在题单里的作用是「确认自己是真的会了」。//// ⚠ 而这道题真正会咬人的地方**一个都不在算法里**(解析页把三条都量过了):// ① 第一行是「n m」=「**总钱数** 在前,**物品数** 在后」—— 和直觉正好相反;// ② 价值是 v × p,不是 p;// ③ 一维必须倒序。//// ★ 顺带一条算术:答案 = Σ v×p ≤ 5 × Σ v ≤ 5n < 1.5 × 10^5 ——// 题面写的「< 10^8」松了六百多倍,int 随便够。
#include <bits/stdc++.h>using namespace std;
int main() { int n, m; // ⚠ n = 总钱数,m = 物品件数 if (!(cin >> n >> m)) return 0;
vector<int> f(n + 1, 0); for (int i = 0; i < m; i++) { int v, p; cin >> v >> p; int w = v * p; // ★ 价值 = 价格 × 重要度 for (int j = n; j >= v; j--) // 倒序:每件只能买一次 f[j] = max(f[j], f[j - v] + w); } cout << f[n] << "\n"; return 0;}点「运行 ▶」看结果
2★★★ m < 25 这个数:这道题的顶格对拍,参照物可以就是暴力
题面写着 m < 25 —— 最多 24 件物品。把它乘出来:
2^24 = 16 777 216
本书前面两次遇到「顶格跑不动暴力」时,各用了一种办法绕开:
| 顶格时的参照物 | 为什么 | |
|---|---|---|
| P1223 排队接水 | 同一个算法换一个类型(long long 版) |
查的是溢出 |
| P1803 线段覆盖 | 换一个复杂度相同、选法完全不同的算法 | n ≤ 10⁶,2ⁿ 作废 |
| P1049 装箱问题 | 折半枚举 O(2^(n/2)·n) |
n ≤ 30,2³⁰ 跑不动 |
| ★ P1060(这道题) | ★ 就是最朴素的 2ᵐ 暴力 |
★ m < 25 ⇒ 2²⁴ 只有 1677 万 |
⇒ 这一次什么都不用绕。 实测顶格(n = 30000、m = 24)60 轮对拍:
2²⁴ 不剪枝 DFS vs 一维倒序 DP |
★ 不一致 0 轮 |
| 那 60 轮里「正序」被抓 | 59 / 60 |
| 那 60 轮里「忘了乘 v」被抓 | 60 / 60 |
★ 而这就是这道题最值得带走的一条:读数据范围的时候顺手把它乘出来。
m < 25 不是背景板,它是出题人递过来的一把参照物。
3三个错法,官方样例一个不剩全挡住了
官方样例 1000 5 / … |
输出 | 挡住了吗 |
|---|---|---|
| 正解 | 3900 | —— |
忘了乘 v(拿 p 当价值) |
12 | ✓ |
读反 n / m |
0 | ✓ |
| 一维正序(= 完全背包) | 5000 | ✓ |
本书从 P1223 起连着八道题量这条规律: 样例挡住的都是「每一组都错」的错法,放过的都是「偶尔才错」的。
这一页三个错法全被挡住,因为它们三个全是「每一组都错」型 (小档 300 轮:正序被抓 224 次、忘了乘 v 被抓 277 次,读反则连输入都读不完整)。 ⇒ 这不是那条规律的反例,是它的另一个极端 —— 第 21 章 P1077 是「三个全放过」(三个全是偶尔才错),正好凑成一对。
★ 而「读反 n / m」这一条还和 P1164 形成对照:
那道题的样例是 4 4,两个数相等 ⇒ 同样的错法在样例上是恒等变换,一点声音都没有。
⇒ 「样例挡不挡得住」的主语是这一组样例长什么样,不是这个 bug 有多严重。
4★★ 把题面那几句约束称一称
按第 12 章那套判据(情报 / 命门 / 噪声,判法是同一个动作: 造一档违反它的数据,看有没有任何一版的行为变了):
| 题面那句话 | 称出来是 | 凭什么 |
|---|---|---|
m < 25 |
★★★ 情报 | 它是「顶格对拍的参照物可以就是暴力」的许可证(第 ② 步) |
「v, p 是非负整数」(v 可以是 0) |
★ 噪声 | 见下面 |
「输出 < 10⁸」 |
★ 松得离谱的情报 | 见下面 |
造一档允许 v = 0 的数据(300 轮里有 78 轮真的含 0):
正解 vs 2ᵐ 暴力不一致的轮数 |
★ 0 |
体积为 0 的物品,价值也是 0 × p = 0 —— 拿不拿都一样,
一维倒序那句 for (j = n; j >= 0; j--) f[j] = max(f[j], f[j] + 0) 什么也没做。
⇒ 它连一版的行为都没改变,是噪声。
算一下真实上界:答案 = Σ vⱼ × pⱼ ≤ 5 × Σ vⱼ ≤ 5n < 5 × 3 × 10⁴ = 1.5 × 10⁵。
| 构造出来的极值(24 件各 1250 元、重要度全 5,正好花完 30000) | ★ 150 000 = 5n,上界可达 |
| 随机顶格 300 轮里最大的答案 | 150 000(★ 只有 1 轮真的达到) |
| 题面写的上界 | 10⁸ |
| 松了 | ★ 666 倍 |
⇒ 和 P1080 国王游戏 正好是一对:那道题的
「保证答案不超过 10⁹」恰好是 unsigned long long 够用的充分条件,一点不松;
而这道题的 10⁸ 是随手写的一个大数。
★ 结论不是「题面的话不可信」,是 「那句保证到底卡在哪儿」要自己乘一遍才知道 ——
乘出来是 1.5 × 10⁵ 之后,「要不要 long long」这个问题连问都不用问。
5度量程序和生成器
6一页纸
| 关键的一步 | 价值 = 价格 × 重要度;其余和第 23 章那份 fast.cpp 一个字不差 |
| 哪一版能 AC | p1060.cpp —— 一维倒序,O(nm) = 7.2 × 10⁵ |
★★★ m < 25 这个数 |
2²⁴ = 1677 万 ⇒ 顶格对拍的参照物可以就是暴力(本书第四种办法) |
| 顶格实测 | 60 轮:暴力 vs DP 不一致 0;正序被抓 59、忘了乘 v 被抓 60 |
| 错法一 | 拿 p 当价值(忘了乘 v)—— 小档被抓 277 / 300 |
| 错法二 | 第一行读反(n 是钱数在前)—— ⚠ 而 P1164 的样例 4 4 让同样的错法隐身 |
| 错法三 | 一维正序 ⇒ 完全背包 —— 小档被抓 224 / 300 |
| ★ 样例 | 三个错法一个不剩全挡住 —— 因为三个全是「每组都错」型(P1077 是反面极端) |
| ★★ 约束称重 | m < 25 = 情报;「v 可为 0」= 噪声(0 轮行为改变);「输出 < 10⁸」= 松了 666 倍 |
| 真实上界 | 5n = 150 000,可达(构造出来正好取到) |