"计数动态规划:探索整数划分问题中的计数技巧与方法"

185 阅读3分钟

整数划分

一个正整数 n𝑛 可以表示成若干个正整数之和,形如:𝑛=𝑛1+𝑛2+…+𝑛𝑘,其中 𝑛1≥𝑛2≥…≥𝑛𝑘, 𝑘≥1。

我们将这样的一种表示称为正整数 𝑛 的一种划分。

现在给定一个正整数 𝑛,请你求出 𝑛 共有多少种不同的划分方法。

输入格式

共一行,包含一个整数 𝑛。

输出格式

共一行,包含一个整数,表示总划分数量。

由于答案可能很大,输出结果请对 10^9^+7 取模。

数据范围

1≤𝑛≤1000

输入样例:

5

输出样例:

7

思路:把1,2,3, … n分别看做n个物体的体积,这n个物体均无使用次数限制,问恰好能装满总体积为n的背包的总方案数(完全背包问题变形)

初值问题: 求最大值时,当都不选时,价值显然是 0 而求方案数时,当都不选时,方案数是 1(即前 i 个物品都不选的情况也是一种方案),所以需要初始化为 1 即:for (int i = 0; i <= n; i ++) f[i][0] = 1; 等价变形后: f[0] = 1

状态计算:

f[i][j]表示前i个整数(1,2…,i)恰好拼成j的方案数 求方案数:把集合选0个i,1个i,2个i,…全部加起来 f[i][j] = f[i - 1][j] + f[i - 1][j - i] + f[i - 1][j - 2 * i] + ...; f[i][j - i] = f[i - 1][j - i] + f[i - 1][j - 2 * i] + ...; 因此 f[i][j]=f[i−1][j]+f[i][j−i]; (这一步类似完全背包的推导)

朴素做法

// f[i][j] = f[i - 1][j] + f[i][j - i]
#include <iostream>

using namespace std;

const int N = 1e3 + 7, mod = 1e9 + 7;

int f[N][N];

int main() {
    int n;
    cin >> n;

    for (int i = 0; i <= n; i ++) {
        f[i][0] = 1; // 容量为0时,前 i 个物品全不选也是一种方案
    }
    
    for (int i = 1; i <= n; i ++) {
        for (int j = 0; j <= n; j ++) {
            f[i][j] = f[i - 1][j] % mod; // 特殊 f[0][0] = 1
            if (j >= i) f[i][j] = (f[i - 1][j] + f[i][j - i]) % mod;
        }
    }
    
    cout << f[n][n] << endl;

}

等价变形

// f[i][j] = f[i - 1][j] + f[i][j - i]
#include <iostream>

using namespace std;

const int N = 1e3 + 7, mod = 1e9 + 7;

int f[N];

int main() {
    int n;
    cin >> n;


    f[0] = 1; // 容量为0时,前 i 个物品全不选也是一种方案
    
    for (int i = 1; i <= n; i ++) {
        for (int j = i; j <= n; j ++) {
            f[j] = (f[j] + f[j - i]) % mod;
        }
    }
    
    cout << f[n] << endl;

}

完全背包解法 O(n^3^)

Java 代码

public class Main{
    public static final int N = 1010;
    public static final int M = (int) (1e9 + 7);

    public static void main(String[] args){
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        int[][] dp = new int[N][N];
        dp[0][0] = 1;//一个数都不选是一种方案
    
        for(int i=1; i<=n; i++){
            for(int j=0; j<=n; j++){
                for(int k=0; j - k * i >= 0; k++){
                    dp[i][j] += dp[i - 1][j - k * i];
                    dp[i][j] %= M;
                }
            }
        }
    
        System.out.println(dp[n][n]);
    }

}

完全背包解法 - 优化 f[i][j] = f[i-1][j] + f[i-1][j-i] + f[i-1][j-2i] + ... + f[i-1][j-si] f[i][j-i] = f[i-1][j-i] + f[i-1][j-2i] + ... + f[i-1][j-si] 替换:f[i][j] = f[i-1][j] + f[i][j-1] 优化一维:f[j] = f[j] + f[j-i]

Java 代码

public class Main{
    public static final int N = 1010;
    public static final int M = (int) (1e9 + 7);

    public static void main(String[] args){
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        int[] dp = new int[N];
        dp[0] = 1;//一个数都不选是一种方案
    
        for(int i=1; i<=n; i++){
            for(int j=i; j<=n; j++){
                dp[j] = (dp[j] + dp[j-i]) % M;
            }
        }
    
        System.out.println(dp[n]);
    }

}

计数类DP

public class Main{
    public static final int N = 1010;
    public static final int M = (int) (1e9 + 7);

    public static void main(String[] args){
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        int[][] dp = new int[N][N];
        dp[0][0] = 1;
    
        for(int i=1; i<=n; i++){
            for(int j=1; j<=i; j++){
                dp[i][j] = (dp[i - 1][j - 1] + dp[i - j][j]) % M;
            }
        }
    
        int res = 0;
        for(int i=1; i<=n; i++){
            res = (res + dp[n][i]) % M;
        }
    
        System.out.println(res);
    }

}