侧边栏壁纸
博主头像
GabrielxD

列車は必ず次の駅へ。では舞台は?私たちは?

  • 累计撰写 674 篇文章
  • 累计创建 128 个标签
  • 累计收到 20 条评论

目 录CONTENT

文章目录

【计数DP】整数划分

GabrielxD
2022-11-30 / 0 评论 / 0 点赞 / 232 阅读 / 1,389 字
温馨提示:
本文最后更新于 2022-12-01,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

题目

900. 整数划分


一个正整数 nn 可以表示成若干个正整数之和,形如: n=n1+n2++nkn = n_1 + n_2 + … + n_k ,其中 n1n2nk,k1n_1 \ge n_2 \ge … \ge n_k, k \ge 1

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

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

输入格式

共一行,包含一个整数 nn

输出格式

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

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

数据范围

1n10001 \le n \le 1000

输入样例:

5

输出样例:

7

解题

方法一:动态规划

思路

用多重背包问题的思路考虑:

nn 个物品与一个容量为 nn 的背包,第 ii 种物品最多有 ii 件,每件体积是 iiii11 开始),求把背包正好填满的方案数(不考虑顺序)。

思维过程:

image-20221129233035761

动态规划:

  • 状态定义:dp[i][j]dp[i][j] 表示所有只考虑前 ii 个物品,且总体积恰好等于 jj 的所有选法的数量(mod109+7\mod 10^9+7)。
  • 状态转移方程:dp[i][j]=k=0min(n,ji)dp[i1][jki]dp[i][j] = \sum_{k=0}^{\min(n, \lfloor\frac{j}{i}\rfloor)} dp[i - 1][j - k * i]
  • 初始状态:只考虑前 00 个物品背包容量为 00 时只有一种固定的方案,所以:dp[0][0]=1dp[0][0] = 1

代码

import java.util.*;
import java.io.*;

public class Main {
    static final int MOD = (int) 1e9 + 7;
    
    public static void main(String[] args) throws IOException {
        StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
        in.nextToken();
        int n = (int) in.nval;
        int[][] dp = new int[n + 1][n + 1];
        dp[0][0] = 1;
        for (int i = 1; i <= n; ++i) {
            for (int j = 0; j <= n; ++j) {
                for (int k = 0; k <= n && k * i <= j; ++k) {
                    dp[i][j] = (dp[i][j] + dp[i - 1][j - k * i]) % MOD;
                }
            }
        }
        System.out.println(dp[n][n]);
    }
}

优化

优化的思路和 完全背包问题 - 优化 一样,都是展开公式、代换。

原式:f[i][j] = f[i][j] + f[i-1][j-k*i]
展开:f[i][j] = f[i-1][j] + f[i-1][j-i] + f[i-1][j-2*i] + ... + f[i-1][j-k*i]
   f[i][j-i] =             f[i-1][j-i] + f[i-1][j-2*i] + ... + f[i-1][j-k*i]
代换:f[i][j] = f[i-1][j] + f[i][j-i]

于是有状态转移方程:dp[i][j]=dp[i1][j]+dp[i][ji]dp[i][j] = dp[i-1][j] + dp[i][j-i]

import java.util.*;
import java.io.*;

public class Main {
    static final int MOD = (int) 1e9 + 7;
    
    public static void main(String[] args) throws IOException {
        StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
        in.nextToken();
        int n = (int) in.nval;
        int[][] dp = new int[n + 1][n + 1];
        dp[0][0] = 1;
        for (int i = 1; i <= n; ++i) {
            for (int j = 0; j <= n; ++j) {
                dp[i][j] = dp[i - 1][j];
                if (j >= i) dp[i][j] = (dp[i][j] + dp[i][j - i]) % MOD;
            }
        }
        System.out.println(dp[n][n]);
    }
}

滚动数组再优化

import java.util.*;
import java.io.*;

public class Main {
    static final int MOD = (int) 1e9 + 7;
    
    public static void main(String[] args) throws IOException {
        StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
        in.nextToken();
        int n = (int) in.nval;
        int[] dp = new int[n + 1];
        dp[0] = 1;
        for (int i = 1; i <= n; ++i) {
            for (int j = i; j <= n; ++j) {
                dp[j] = (dp[j] + dp[j - i]) % MOD;
            }
        }
        System.out.println(dp[n]);
    }
}
#include <iostream>

using namespace std;

const int N = 1010, MOD = 1e9 + 7;
int n;
int dp[N];

int main() {
    scanf("%d", &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]) % MOD;
        }
    }
    printf("%d\n", dp[n]);
    
    return 0;
}

方法二:另一种思路的动态规划

思路

思维过程:

image-20221130000803321

动态规划:

  • 状态定义:dp[i][j]dp[i][j] 表示所有总和是 ii ,且恰好用了 jj 个数的方案的数量( mod109+7\mod 10^9+7 )。
  • 状态转移方程:dp[i][j]=dp[i1][j1]+dp[ij][j]dp[i][j] = dp[i - 1][j - 1] + dp[i - j][j]
  • 初始状态:dp[0][0]=1dp[0][0] = 1

要求的是所有总和为 nn,且恰好用了任意个数的方案的数量,即:i=1ndp[n][i]\sum_{i=1}^{n}{dp[n][i]}

代码

import java.util.*;
import java.io.*;

public class Main {
    static final int MOD = (int) 1e9 + 7;
    
    public static void main(String[] args) throws IOException {
        StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
        in.nextToken();
        int n = (int) in.nval;
        int[][] dp = new int[n + 1][n + 1];
        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]) % MOD;
            }
        }
        int ans = 0;
        for (int i = 1; i <= n; ++i) ans = (ans + dp[n][i]) % MOD;
        System.out.println(ans);
    }
}
#include <iostream>

using namespace std;

const int N = 1010, MOD = 1e9 + 7;
int n;
int dp[N][N];

int main() {
    scanf("%d", &n);
    dp[0][0] = 1;
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= n; ++j) {
            dp[i][j] = dp[i - 1][j - 1];
            if (j <= i) dp[i][j] = (dp[i][j] + dp[i - j][j]) % MOD;
        }
    }
    int ans = 0;
    for (int i = 1; i <= n; ++i) ans = (ans + dp[n][i]) % MOD;
    printf("%d\n", ans);
    
    return 0;
}
0

评论区