题单 · 习题解析

洛谷 P1060 [NOIP 2006 普及组] 开心的金明

★★★ 题面那句 m < 25 是出题人递过来的参照物:2²⁴ = 1677 万 ⇒ 顶格对拍的参照物可以就是暴力;而「输出 < 10⁸」松了 666 倍(真实上界 5n)

原题:洛谷 P1060出自 第 23 章 01 背包 的题单题面本地存档:2026-08-29
⚠ 先自己写一遍,再往下看

这一页不是标准答案,是一条阶梯:把大多数人真实会写出的第一版摆出来, 看它错在哪、慢在哪,再一步一步改。所以它对「已经自己动手撞过一次墙」的人最有用。
还没写过的话,先去写 —— 写出来的哪怕是错的,也比直接看这一页收获大。

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, mn < 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.cpp★ 这一版就能 AC
// 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;
}
点一下即可编辑
输入(stdin)
输出
点「运行 ▶」看结果

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 ≤ 302³⁰ 跑不动
P1060(这道题) 就是最朴素的 2ᵐ 暴力 m < 252²⁴ 只有 1677 万

这一次什么都不用绕。 实测顶格(n = 30000m = 24)60 轮对拍:

2²⁴ 不剪枝 DFS vs 一维倒序 DP 不一致 0 轮
那 60 轮里「正序」被抓 59 / 60
那 60 轮里「忘了乘 v」被抓 60 / 60

★ 而这就是这道题最值得带走的一条:读数据范围的时候顺手把它乘出来。 m < 25 不是背景板,它是出题人递过来的一把参照物。

p1060Brute.cpp顶格参照物:2^m 不剪枝 DFS

3三个错法,官方样例一个不剩全挡住了

p1060NoMul.cpp错法一:忘了乘价格
p1060Swap.cpp错法二:第一行两个数读反
p1060Up.cpp错法三:一维正序
官方样例 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」实测是噪声

造一档允许 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) 什么也没做。 ⇒ 它连一版的行为都没改变,是噪声。

★★ 而「输出 < 10⁸」那句保证,是真的随口写的

算一下真实上界:答案 = Σ 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度量程序和生成器

p1060Count.cpp度量程序(本页所有数字都出自它)
p1060Gen.cpp数据生成器

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可达(构造出来正好取到)