题目链接:https://www.luogu.com.cn/problem/P2089
题目大意:
有 \(10\) 种配料,每种配料可以放 \(1 \sim 3\) 克。现在要凑成共 \(n\) 克的配料。问:
- 有多少种不同的方案?
- 按字典序从小到大输出所有方案。
解题思路:
我们可以设计一个递归函数 void put(int d)
。其中 \(d\) 表示我目前正在考虑放多少克第 \(d\) 种配料,此时我有三种选择:
- 放 \(1\) 克第 \(d\) 种配料,然后接着去放第 \(d+1\) 种配料;
- 放 \(2\) 克第 \(d\) 种配料,然后接着去放第 \(d+1\) 种配料;
- 放 \(3\) 克第 \(d\) 种配料,然后接着去放第 \(d+1\) 种配料。
这个很容易用算法实现,如果我们开一个数组 \(a\),并用 \(a[d]\) 记录第 \(d\) 种配料放了多少克的话,则此时我们只需要:
- 在
put(d)
函数中从 \(1\) 到 \(3\) 枚举 \(i\)(它表示第 \(d\) 种配料放了 \(i\) 克) - 然后去放第 \(d+1\) 种配料即可。
代码的整体逻辑如下:
void put(int d) {for (int i = 1; i <= 3; i++) {a[d] = i; // 第d种配料放了i克dfs(d+1);}
}
但是此时我们只是设计了递归的逻辑,并没有考虑何时递归会结束(因为递归肯定是需要结束的),所以接着我们来思考递归何时结束。
当我们调用 put(d)
时,此时我们正在考虑第 \(d\) 种配料放多少克,同时,也说明我们已经放好了第 \(1, 2, \ldots, d-1\) 种配料(所以现在才会在考虑第 \(d\) 种配料)。
所以递归的边界条件可以定为 \(d \gt 10\)(即 \(d = 11\))时,此时前 \(d-1\)(即前 \(10\) 种调料)都已经放好了,我们只需要统计以下 \(\sum\limits_{i=1}^{10} a[i]\) 是否等于 \(n\) 即能够确定它是否是一种合法的方案了。
但是本题中我们有两个问题要求解:
- 计算总共有多少种不同的方案;
- 按照字典序从小到大输出每一种方案。
这两个逻辑对应的递归边界条件不一样。
1. 计算总共有多少种不同的方案
如果是计算有多少中不同的方案数,则可以开一个全局变量 ans
,每次碰到一个满足条件的方案,执行 ans++
:
void put(int d) {if (d == 11) { // 边界条件:10种配料都选好了int sum = 0; // 开个累加器统计10种配料的重量和for (int i = 1; i <= 10; i++)sum += a[i];if (sum == n) { // 合法的方案ans++;}return; // 递归边界,处理好后要返回,不需要继续递归了}for (int i = 1; i <= 3; i++) {a[d] = i; // 第d种配料放了i克dfs(d+1);}
}
2. 按照字典序从小到大输出每一种方案
如果要输出所有方案,则我们只需要将上述代码中 ans++;
这条语句修改为输出 \(a[1], a[2], \ldots, a[10]\) 的逻辑即可。
void put(int d) {if (d == 11) { // 10中配料都选好了...if (sum == n) { // 合法的方案for (int i = 1; i <= 10; i++)cout << a[i] << " ";cout << endl; // 输出该合法方案}return; // 递归边界,处理好后要返回,不需要继续递归了}for (int i = 1; i <= 3; i++) { ...
(注:省略的部分都一样)
所以我们会发现,两种情况下递归函数 put(d)
除了边界条件的处理不一样,其它处理都是一模一样的。
如何解决这个问题呢?
一种方法是写两个递归函数,但是那样代码会显得很冗杂。
另一种方法是给 put
函数增加一个参数,即 void put(int d, bool flag)
,
- 当
flag
为 true 时,使ans++;
- 当
flag
为 false 时,输出这个方案。
修改后的 put(d, flag)
函数如下:
void put(int d, bool flag) {if (d == 11) { // 10中配料都选好了int sum = 0; // 开个累加器统计10种配料的重量和for (int i = 1; i <= 10; i++)sum += a[i];if (sum == n) { // 合法的方案if (flag) // 方案数 +1ans++;else { // 输出这种方案for (int i = 1; i <= 10; i++)cout << a[i] << " ";cout << endl;}}return; // 递归边界,处理好后要返回,不需要继续递归了}for (int i = 1; i <= 3; i++) {a[d] = i; // 第d种配料放了i克put(d+1, flag);}
}
接着我们只需要:
- 调用
puts(1, true)
,函数调用结束时输出ans
即为总的方案数; - 调用
puts(1, false)
—— 它能够输出所有方案。
完整的代码如下:
#include <bits/stdc++.h>
using namespace std;
int a[11], n, ans;void put(int d, bool flag) {if (d == 11) { // 10中配料都选好了int sum = 0; // 开个累加器统计10种配料的重量和for (int i = 1; i <= 10; i++)sum += a[i];if (sum == n) { // 合法的方案if (flag) // 方案数 +1ans++;else { // 输出这种方案for (int i = 1; i <= 10; i++)cout << a[i] << " ";cout << endl;}}return; // 递归边界,处理好后要返回,不需要继续递归了}for (int i = 1; i <= 3; i++) {a[d] = i; // 第d种配料放了i克put(d+1, flag);}
}int main() {cin >> n;put(1, true);cout << ans << endl;put(1, false);return 0;
}