题目链接:https://www.luogu.com.cn/problem/P2089

题目大意:

有 \(10\) 种配料,每种配料可以放 \(1 \sim 3\) 克。现在要凑成共 \(n\)

  1. 有多少种不同的方案?
  2. 按字典序从小到大输出所有方案。

解题思路:

我们可以设计一个递归函数 void put(int d)。其中 \(d\) 表示我目前正在考虑放多少克第 \(d\)

  1. 放 \(1\) 克第 \(d\) 种配料,然后接着去放第 \(d+1\)
  2. 放 \(2\) 克第 \(d\) 种配料,然后接着去放第 \(d+1\)
  3. 放 \(3\) 克第 \(d\) 种配料,然后接着去放第 \(d+1\)

这个很容易用算法实现,如果我们开一个数组 \(a\),并用 \(a[d]\) 记录第 \(d\)

  1. put(d) 函数中从 \(1\) 到 \(3\) 枚举 \(i\)(它表示第 \(d\) 种配料放了 \(i\)
  2. 然后去放第 \(d+1\)

代码的整体逻辑如下:

void put(int d) {
    for (int i = 1; i <= 3; i++) {
        a[d] = i; // 第d种配料放了i克
        put(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. 计算总共有多少种不同的方案;
  2. 按照字典序从小到大输出每一种方案。

这两个逻辑对应的递归边界条件不一样。

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)   // 方案数 +1
                ans++;
            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);
    }
}

接着我们只需要:

  1. 调用 puts(1, true),函数调用结束时输出 ans 即为总的方案数;
  2. 调用 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)   // 方案数 +1
                ans++;
            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;
}