














GESP C++ 三级、四级经典练习题,考察多重循环枚举、递归回溯搜索(DFS)以及剪枝优化。题目要求找出 10 种配料(每种 1~3 克)使其质量之和恰好等于目标美味度 $n$ 的所有可能方案,并按字典序输出。本题规模适中($3^{10} = 59049$ 种状态),既是初学者练习多层嵌套循环的绝佳载体,也是迈入递归回溯、状态树搜索和可行性剪枝大门的“新手村第一题”。题目难度⭐⭐☆☆☆,洛谷难度等级入门 / 普及−。
猪猪 Hanke 得到了一只鸡。
猪猪 Hanke 特别喜欢吃烤鸡(本是同畜牲,相煎何太急!)Hanke 吃鸡很特别,为什么特别呢?因为他有 $10$ 种配料(芥末、孜然等),每种配料可以放 $1$ 到 $3$ 克,任意烤鸡的美味程度为所有配料质量之和。
现在, Hanke 想要知道,如果给你一个美味程度 $n$ ,请输出这 $10$ 种配料的所有搭配方案。
一个正整数 $n$,表示美味程度。
第一行,方案总数。
第二行至结束,$10$ 个数,表示每种配料所放的质量,按字典序排列。
如果没有符合要求的方法,就只要在第一行输出一个 $0$。
1
2
3
4
5
6
7
8
9
10
11
10
1 1 1 1 1 1 1 1 1 2
1 1 1 1 1 1 1 1 2 1
1 1 1 1 1 1 1 2 1 1
1 1 1 1 1 1 2 1 1 1
1 1 1 1 1 2 1 1 1 1
1 1 1 1 2 1 1 1 1 1
1 1 1 2 1 1 1 1 1 1
1 1 2 1 1 1 1 1 1 1
1 2 1 1 1 1 1 1 1 1
2 1 1 1 1 1 1 1 1 1
对于 $100\%$ 的数据,$n \leq 10000$。
这道题目是一道非常经典的组合方案枚举与回溯搜索问题。我们需要为 10 种配料确定具体质量(每种质量只能为 1、2 或 3 克),要求 10 种配料的质量总和等于给定的美味程度 $n$。
我们先从数学极值的角度分析本题的取值范围:
因此,能够凑出的美味程度 $n$ 的取值区间严格限制在 $[10, 30]$ 内:
0 即可结束程序。题目数据范围给到 $n \le 10000$,加入此特判后,绝大多数无效输入(例如 $n = 5$ 或 $n = 100$ 等)都可以在 $\mathcal{O}(1)$ 时间内瞬间完成判断,无需进行任何搜索或循环。
若 $n \in [10, 30]$,我们需要列举出所有可行的搭配。
题目要求输出的方案“按字典序排列”。
无论是多重循环还是深度优先搜索,只要按照从前往后、从小到大的顺序枚举,遍历生成方案的先后次序与字典序完全一致,不需要使用任何额外的排序操作!
题目要求第一行输出“方案总数”,接下来才输出具体方案。由于我们在枚举初期无法直接得知最终满足条件的方案数,通常有两种处理方式:
std::vector<std::vector<int>> 或者全局二维数组 int ans[10000][10]。枚举过程中只要发现和为 $n$ 的合法方案,就将其存入数组中。遍历结束后,方案总数即为数组长度,先输出总数,再逐行输出各个方案。这是最朴素、最容易理解的直观解法:
for 循环,循环变量分别表示 10 种配料的质量;优缺点分析:
深度优先搜索(DFS)是解决排列、组合与枚举类问题的标准通用模板:
void dfs(int step, int current_sum)step:当前正在决策第几种配料($0 \sim 9$);current_sum:当前已经放置的配料质量总和。step == 10 时,10 种配料已全部决策完毕;current_sum == n 是否成立,若成立则将当前路径存入答案数组,随后 return 回溯。dfs(step + 1, current_sum + w) 进入下一层;在搜索过程中,并不是所有分支都有必要走到底。若中途就能断定当前分支不可能凑出目标和 $n$,应立即返回以节省时间:
step 种配料,剩余 $10 - step$ 种配料未选;return 即可!加入可行性剪枝后,无效分支在浅层即被切除,搜索树节点访问量可从几万次缩减至几百次。
0 并且不产生多余的垃圾输出或越界访问。0 后输出空行或多余的空格字符。std::vector<int> path 记录路径,递归深入前执行 path.push_back(w),递归返回后必须配对执行 path.pop_back()。path[step] = w,由于每一层只使用对应下标 path[step],返回后会被下一轮覆盖,但如果在全局变量中维护了累加和 sum,回溯时必须保证配对扣除 sum -= w。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <iostream>
#include <vector>
using namespace std;
int n;
vector<int> path; // 记录当前递归路径上的配料质量
vector<vector<int>> results; // 暂存所有满足条件的搭配方案
// step: 当前正在决定第 step 种配料 (0 ~ 9)
// current_sum: 当前已放入配料的质量之和
void dfs(int step, int current_sum) {
// 递归边界:10 种配料均已决策完毕
if (step == 10) {
if (current_sum == n) {
results.push_back(path);
}
return;
}
// 可行性剪枝:
// 剩余未决策的配料数为 10 - step
// 即使剩余全部放最小值 1 克,或者全部放最大值 3 克
int remaining = 10 - step;
if (current_sum + remaining * 1 > n || current_sum + remaining * 3 < n) {
return; // 无论后续怎么放都不可能达到目标 n,直接回溯
}
// 按升序 1 -> 2 -> 3 尝试每种可能的配料质量,保证字典序
for (int weight = 1; weight <= 3; ++weight) {
path.push_back(weight); // 1. 做出选择
dfs(step + 1, current_sum + weight); // 2. 深入下一层
path.pop_back(); // 3. 撤销选择(恢复现场)
}
}
int main() {
// 提高输入输出效率
ios::sync_with_stdio(false);
cin.tie(nullptr);
if (!(cin >> n)) {
return 0;
}
// 极值特判:10种配料最小和为10,最大和为30
if (n < 10 || n > 30) {
cout << 0 << "\n";
return 0;
}
// 从第 0 种配料、当前总和为 0 开始深搜
dfs(0, 0);
// 第一行输出方案总数
cout << results.size() << "\n";
// 逐行输出每种搭配方案
for (const auto& scheme : results) {
for (int i = 0; i < 10; ++i) {
cout << scheme[i] << (i == 9 ? "" : " ");
}
cout << "\n";
}
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <iostream>
#include <vector>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
if (!(cin >> n)) {
return 0;
}
// 边界特判
if (n < 10 || n > 30) {
cout << 0 << "\n";
return 0;
}
// 暂存所有合法方案
vector<vector<int>> results;
// 10 重嵌套循环直接枚举每种配料的克数 (1 ~ 3)
for (int a = 1; a <= 3; ++a) {
for (int b = 1; b <= 3; ++b) {
for (int c = 1; c <= 3; ++c) {
for (int d = 1; d <= 3; ++d) {
for (int e = 1; e <= 3; ++e) {
for (int f = 1; f <= 3; ++f) {
for (int g = 1; g <= 3; ++g) {
for (int h = 1; h <= 3; ++h) {
for (int i = 1; i <= 3; ++i) {
for (int j = 1; j <= 3; ++j) {
if (a + b + c + d + e + f + g + h + i + j == n) {
results.push_back({a, b, c, d, e, f, g, h, i, j});
}
}
}
}
}
}
}
}
}
}
}
// 输出方案总数
cout << results.size() << "\n";
// 逐行输出方案
for (const auto& scheme : results) {
for (int k = 0; k < 10; ++k) {
cout << scheme[k] << (k == 9 ? "" : " ");
}
cout << "\n";
}
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <iostream>
using namespace std;
int n;
int path[10]; // 记录当前方案的10个配料值
int ans[10000][10]; // 二维数组存储所有方案(最大方案数小于9000)
int total_count = 0; // 方案计数器
void dfs(int step, int current_sum) {
if (step == 10) {
if (current_sum == n) {
for (int i = 0; i < 10; ++i) {
ans[total_count][i] = path[i];
}
total_count++;
}
return;
}
// 可行性剪枝
int rem = 10 - step;
if (current_sum + rem * 1 > n || current_sum + rem * 3 < n) {
return;
}
for (int w = 1; w <= 3; ++w) {
path[step] = w; // 覆盖当前位置,无需显式 pop
dfs(step + 1, current_sum + w);
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
if (!(cin >> n)) {
return 0;
}
if (n < 10 || n > 30) {
cout << 0 << "\n";
return 0;
}
dfs(0, 0);
cout << total_count << "\n";
for (int i = 0; i < total_count; ++i) {
for (int j = 0; j < 10; ++j) {
cout << ans[i][j] << (j == 9 ? "" : " ");
}
cout << "\n";
}
return 0;
}
| 实现方式 | 时间复杂度 | 空间复杂度 | 适用场景与优缺点 |
|---|---|---|---|
| 十重循环暴力枚举 | $\mathcal{O}(3^{10}) \approx 5.9 \times 10^4$ | $\mathcal{O}(K)$ | 简单直接,GESP 三级学生最易上手;但嵌套代码层级过深,缺乏灵活性 |
| 基础 DFS 回溯 | $\mathcal{O}(3^{10}) \approx 5.9 \times 10^4$ | $\mathcal{O}(D + K)$ | GESP 四级核心模板,结构优雅,易于扩展到 $M$ 种配料或变长约束 |
| DFS + 可行性剪枝 | 远小于 $\mathcal{O}(3^{10})$(节点数 $\le 1000$) | $\mathcal{O}(D + K)$ | 最推荐做法,运行时间 $< 2\text{ms}$,展示了算法竞赛中的核心剪枝思维 |
(注:$D = 10$ 为递归树深度,$K \le 8953$ 为存储合法方案的空间大小)
知识点拓展与思维升华:
所有代码已上传至Github:https://github.com/lihongzheshuai/yummy-code
GESP 学习专题站:GESP WIKI
"luogu-"系列题目可在洛谷题库进行在线评测。
"bcqm-"系列题目可在编程启蒙题库进行在线评测。
欢迎加入:Java、C++、Python技术交流QQ群(982860385),大佬免费带队,有问必答
欢迎加入:C++ GESP/CSP认证学习QQ频道,考试资源总结汇总
欢迎加入:C++ GESP/CSP学习交流QQ群(688906745),考试认证学员交流,互帮互助
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。