跳转至

P2089 烤鸡

题目背景

猪猪 Hanke 得到了一只鸡。

题目描述

猪猪 Hanke 特别喜欢吃烤鸡(本是同畜牲,相煎何太急!)Hanke 吃鸡很特别,为什么特别呢?因为他有 \(10\) 种配料(芥末、孜然等),每种配料可以放 \(1\) 到 \(3\) 克,任意烤鸡的美味程度为所有配料质量之和。

现在, Hanke 想要知道,如果给你一个美味程度 \(n\) ,请输出这 \(10\) 种配料的所有搭配方案。

输入格式

一个正整数 \(n\),表示美味程度。

输出格式

第一行,方案总数。

第二行至结束,\(10\) 个数,表示每种配料所放的质量,按字典序排列。

如果没有符合要求的方法,就只要在第一行输出一个 \(0\)。

输入输出样例 #1

输入 #1

11

输出 #1

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\)。

📌 题目分析

给定数据:

n ≤ 10000(实际上 n > 30 就不可能了)
10 种配料,每种 1 到 3 克

求出所有配料总和等于 n 的方案数,并按字典序输出所有方案。

👉 本质:

枚举所有可能的情况并验证总和

👉 类型:

深度优先搜索(DFS) / 暴力枚举

Note

我的做法,我看童老师ppt上和我的思路差不多,但是语法细节上,童老师用了带参的宏定义,然后细节上童老师还用了更细化的剪枝

我的做法->最无脑的暴力

#include <iostream>

using namespace std;
int main() {
    int n, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9;
    cin >> n;
    //简单剪枝
    if (n < 10 || n>30) {
        cout << "不能合理分配";
    }else{
        for (int a0 = 1;a0 <= 3;a0++) {
            for (int a1 = 1;a1 <= 3;a1++) {
                for (int a2 = 1;a2 <= 3;a2++) {
                    for (int a3 = 1;a3 <= 3;a3++) {
                        for (int a4 = 1;a4 <= 3;a4++) {
                            for (int a5 = 1;a5 <= 3;a5++) {
                                for (int a6 = 1;a6 <= 3;a6++) {
                                    for (int a7 = 1;a7 <= 3;a7++) {
                                        for (int a8 = 1;a8 <= 3;a8++) {
                                            for (int a9 = 1;a9 <= 3;a9++) {
                                                if (a0 + a1 + a2 + a3 + a4 +
                                                    a5 + a6 + a7 + a8 + a9 == n) {
                                                    printf("%d %d %d %d %d %d %d %d %d %d\n"
                                                        , a0, a1, a2, a3, a4, a5, a6, a7, a8, a9);
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    return 0;
}

🧠 解题思路

1️⃣ 为什么可以使用暴力搜索

这道题看起来 n 很大(10000),但实际上配料的限制非常严格: 一共 10 种配料,每种最多放 3 克。 这意味着所有的组合总数只有:

3^10 = 59049 种

五万多次的计算对于 C++ 来说连 1 毫秒都用不到,所以直接暴力把所有情况试一遍是最优解。

2️⃣ 怎样保证字典序输出

字典序就是从小到大排序输出。 我们在进行暴力搜索(或者 DFS)的时候,只要规定每次尝试的顺序是先放 1 克,再放 2 克,最后放 3 克。 因为按固定顺序往下搜,天然找出来的方案就已经是完全符合字典序的了,不需要做任何额外的排序。

3️⃣ 怎样满足题目的输出格式

题目要求:

第一行输出方案总数
接下来输出所有方案

这就意味着我们不能边找边输出(因为你还没找完,不知道总数是多少)。 我们需要一个大容器(比如二维数组或者 vector)把所有合法的方案先存起来。等全部搜完后,先输出容器里存了多少个方案,然后再把容器里的方案挨个打印出来。

4️⃣ 特殊情况(剪枝)

10 种配料,每种最少 1 克,最多 3 克。 所以总和必定在:

10 ~ 30 之间

如果输入的 n 小于 10 或者大于 30,直接输出 0 即可,连搜索都可以省了。


💻 C++代码

#include <iostream>
#include <vector>
using namespace std;

int n;
vector<vector<int>> ans; // 用二维 vector 保存所有合法的方案
vector<int> path;        // 保存当前正在尝试的搭配方案

// step: 当前正在选第几种配料 (1~10)
// sum: 当前配料的总和
void dfs(int step, int sum) {
    // 简单剪枝:如果当前总和已经超过了要求的 n,后面的都不用试了
    if (sum > n) return; 

    // 边界条件:当 10 种配料都选完了(准备选第 11 种时)
    if (step == 11) {
        if (sum == n) {
            ans.push_back(path); // 找到一个合法方案,存下来
        }
        return;
    }

    // 暴力尝试当前配料放 1克、2克、3克
    for (int i = 1; i <= 3; i++) {
        path.push_back(i);           // 把 i 克加进去
        dfs(step + 1, sum + i);      // 继续搜下一种配料
        path.pop_back();             // 回溯:把刚刚加的拿出来,换下一种克数试
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n;

    // 只有在合理范围内才去搜
    if (n >= 10 && n <= 30) {
        dfs(1, 0);
    }

    // 1. 先输出总方案数(如果没有就是 0)
    cout << ans.size() << "\n";

    // 2. 挨个输出存下来的合法方案
    for (int i = 0; i < ans.size(); i++) {
        for (int j = 0; j < 10; j++) {
            cout << ans[i][j] << " ";
        }
        cout << "\n";
    }

    return 0;
}

⚠️ 易错点

❌ 1. 边找边输出导致全错

很多人在 dfs 到了目标后直接 cout << path[i]。 这会导致你的方案直接打在屏幕上,而题目要求第一行必须是总数,输出顺序错了会直接判 0 分(WA)。必须用数组存起来。

❌ 2. 递归深度搞错

配料有 10 种。如果你的层数是从 1 开始算,当 step == 11 时才说明前面的 1 到 10 种都已经选定了。 不能写成 step == 10 时就结算,那样会漏掉第 10 种配料的选择。

❌ 3. 忘记回溯

这是初学者写 DFS 最容易犯的错误:

path.pop_back();

如果不把上一次尝试的克数拿出来(退栈),你的数组里就会不停地堆积无用的克数,导致后续验证全部失效。


🚀 一句话总结

状态空间极小,直接无脑 DFS + 回溯,用 vector 兜住所有答案最后按格式输出即可。

🔥 补充

关于终极暴力: 其实这道题如果你完全不会写 DFS 递归,写一个 10 层的 for 循环嵌套 也是绝对满分通过的(而且速度同样极快)。

就像这样:

for(int a=1; a<=3; a++)
  for(int b=1; b<=3; b++)
    ...
      for(int j=1; j<=3; j++)
        if(a+b+...+j == n) // 存起来
虽然这种代码看起来像“大炮打蚊子”,但它最纯粹地体现了“暴力枚举”的本质。在比赛中如果时间紧迫且层数固定,十层循环也是一种“实用且稳妥”的策略!