- 问题背景
-
解法一
-
Dp分析
-
状态表示
- 二维状态表示
f(i,j) f(i,j)表示的是哪一个集合:所有满足如下条件的集合- 将其看做成完全背包问题,物品就是从
1到n,每个数可以选无限次 - 从前
i个数中选,它们和恰好是j的选法
- 将其看做成完全背包问题,物品就是从
f(i,j)存的是什么属性:Max,Min,数量;在这里f(i,j)存的应该是数量,即从前i个数中选,它们和恰好是j的选法数量
- 二维状态表示
-
状态计算:
f(i,j)可以怎么算出来?- 这里就类似完全背包问题,将集合分成
k+1个子集- 每次选择
k个第i个数,k从0取到 k * i <= j - f(i, j) += f(i - 1, j - k * i)
- 每次选择
- 这里就类似完全背包问题,将集合分成
-
代码
-
//初始化 //只选1,总和为i的选法数量为1 //从前i个数中选,总和为0的选法数量为1,即什么都不选 for (int i = 1; i <= n; i++) { f[1][i] = 1; f[i][0] = 1; } for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { for (int k = 0; k * i <= j; k++) { 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 - 3 * i) + ……
- f(i, j - i) = f(i - 1, j - i) + f(i - 1, j - 2 * i) + f(i - 1, j - 3 * i) + ……
- 由上面两式,可以得出 f(i, j) = f(i - 1, j) + f(i, j - i),与k无关
-
因此可以做如下优化
- 将
k层循环删掉
- 将
-
代码
-
//初始化 //只选1,总和为i的选法数量为1 //从前i个数中选,总和为0的选法数量为1,即什么都不选 for (int i = 1; i <= n; i++) { f[1][i] = 1; f[i][0] = 1; } for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { f[i][j] = f[i - 1][j]; if (j >= i) { f[i][j] += f[i][j - i]; } } }
-
-
-
再优化
-
观察状态计算的过程,可以发现两个特点
- f(i, j) 首先会继承 f(i - 1, j) 的值
- 然后再加上当前第
i层在它之前就更新完了的 f(i, j - i) 的值
-
因此可以做如下优化
- 将 i 这一维直接优化掉,i 的每一层循环中的 j 都在原先的 j 上进行覆盖
- j 层循环直接从 i 开始
-
代码
-
//初始化 f[0] = 1; for (int i = 1; i <= n; i++) { for (int j = i; j <= n; j++) { f[j] += f[j - i]; } }
-
-
-
解法二
-
Dp分析
-
状态表示
- 二维状态表示
f(i,j) f(i,j)表示的是哪一个集合:所有满足如下条件的集合- 所有总和为
i,并且恰好由j个数组成的选法
- 所有总和为
f(i,j)存的是什么属性:Max,Min,数量;在这里f(i,j)存的应该是数量,即所有总和为i,并且恰好由j个数组成的选法数量
- 二维状态表示
-
状态计算:
f(i,j)可以怎么算出来?- 将集合分为两个子集
- 选择的数中最小值为1
- f(i - 1, j - 1)
- 选择的数中最小值大于1
- f(i - j, j)
- j 从
1取到i - 1,但是还是写成j <= i,避免i=1时j取不到数
- 选择的数中最小值为1
- 因此 f(i, j) = f(i - 1, j - 1) + f(i - j, j)
- 将集合分为两个子集
-
代码
-
//总和为0,并且恰好由0个数组成的选法为1 f[0][0] = 1; for (int i = 1; i <= n; i++) { for (int j = 1; j <= i; j++) { f[i][j] = f[i - 1][j - 1] + f[i - j][j]; } } int ans = 0; for (int i = 1; i <= n; i++) { ans += f[n][i]; }
-
练习
01 整数划分
- 题目
- 思路一题解一
import java.io.*;
import java.util.*;
public class Main {
public static final int N = 1010;
public static final int mod = (int) 1e9 + 7;
//即从前i个数中选,它们和恰好是j的选法 的数量
public static int[][] f = new int[N][N];
public static int n;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
n = Integer.parseInt(br.readLine());
//初始化
//只选1,总和为i的选法数量为1
//从前i个数中选,总和为0的选法数量为1,即什么都不选
for (int i = 1; i <= n; i++) {
f[1][i] = 1;
f[i][0] = 1;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
for (int k = 0; k * i <= j; k++) {
f[i][j] = (f[i][j] + f[i - 1][j - k * i]) % mod;
}
}
}
pw.println(f[n][n]);
br.close();
pw.close();
}
}
- 思路一题解二
import java.io.*;
import java.util.*;
public class Main {
public static final int N = 1010;
public static final int mod = (int) 1e9 + 7;
//即从前i个数中选,它们和恰好是j的选法 的数量
public static int[][] f = new int[N][N];
public static int n;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
n = Integer.parseInt(br.readLine());
//初始化
//只选1,总和为i的选法数量为1
//从前i个数中选,总和为0的选法数量为1,即什么都不选
for (int i = 1; i <= n; i++) {
f[1][i] = 1;
f[i][0] = 1;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
f[i][j] = f[i - 1][j];
if (j >= i) {
f[i][j] = (f[i][j] + f[i][j - i]) % mod;
}
}
}
pw.println(f[n][n]);
br.close();
pw.close();
}
}
- 思路一题解三
import java.io.*;
import java.util.*;
public class Main {
public static final int N = 1010;
public static final int mod = (int) 1e9 + 7;
//即从前i个数中选,它们和恰好是j的选法 的数量
public static int[] f = new int[N];
public static int n;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
n = Integer.parseInt(br.readLine());
//初始化
f[0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j++) {
f[j] = (f[j] + f[j - i]) % mod;
}
}
pw.println(f[n]);
br.close();
pw.close();
}
}
- 思路二题解四
import java.io.*;
import java.util.*;
public class Main {
public static final int N = 1010;
public static final int mod = (int) 1e9 + 7;
//所有总和为i 并且恰好由j个数组成的选法 的数量
public static int[][] f = new int[N][N];
public static int n;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
PrintWriter pw = new PrintWriter(new OutputStreamWriter(System.out));
n = Integer.parseInt(br.readLine());
//总和为0,并且恰好由0个数组成的选法为1
f[0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
f[i][j] = (f[i - 1][j - 1] + f[i - j][j]) % mod;
}
}
int ans = 0;
for (int i = 1; i <= n; i++) {
ans = (ans + f[n][i]) % mod;
}
pw.println(ans);
br.close();
pw.close();
}
}