整数划分
C++
整数划分
/*
* 整数划分
*
* 问题描述:
* 题目:
* 一个正整数 n 可以表示成若干个正整数之和,形如:n=n1+n2+…+nk,其中, n1 ≥ n2 ≥ … ≥ nk,k ≥ 1。
* 我们将这样的一种表示称为正整数 n 的一种划分。(其实上面这个定义主要是说明其有序性)
* 现在给定一个正整数 n,请你求出 n 共有多少种不同的划分方法。
*
* 数据范围:
* 1 ≤ n ≤ 1000
* 求解思路1:
* f[i][j] 表示 若干整数相加为 i,其中最大的正整数为 j,划分方法的次数
* 那么: f[i][j] = f[i - j][1] + f[i - j][2] + ... + f[i - j][min(i - j, j)]
* 其中 min(i - j, j) 既是为了防止最大值超过 sum,也是为了防止最大值超过 j
* 初始化时候 memset(f, 0, sizeof f), f[0][0] = 1即可。
*
* 复杂度 O(N^3)
* 求解思路2:
* 不难发现求解思路1复杂度过高在于f[i][j] 的求解公式是一个 sum,因此我们可以重新定义 f[i][j]数组含义减少复杂度,
* 或者是维护一个 sum 的累加和数组。
* 重新定义 f[i][j], 表示 若干整数相加为 i,其中最大的正整数 <= j,划分方法的次数
* 因此
* f[i][j] = f[i][j - 1] + f[i - j][min(i - j, j)]
* 复杂度 O(N ^ 2)
* 最后发现,这是完全背包解法
* 求解思路3:
* f[i][j] 表示总和为 i,数组的长度为 j 的方案数
* f[i][j] = f[i - 1][j - 1] + f[i - 2][j - 1] + f[i - 3][j - 1] + ... + f[j - 1][j - 1] + ... + f[0][j - 1]
*
* 算法优化: f[i][j] 表示 总和小于等于 i,长度等于 j
* f[i][j] = f[i - 1][j] + f[i-1][j-1]
*
* 初始化:
* memset(f, 0, sizeof f);
* f[0][0] = 1
*
* 但是该方法是错误的,因为 f[i - 1][j - 1] + f[i - 2][j - 1] + f[i - 3][j - 1] + ... + f[j - 1][j - 1] + ... + f[0][j - 1]
* 这些方案可能是存在交叉了。。。。
*
* 重新划分,f[i][j]还是表示总和为 i,长度为 j
* 存在 1 的方案个数为 f[i-1][j-1]
* 不存在 1 的方案个数为 f[i-j][j]
*/
#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <cstdio>
#include <cmath>
using namespace std;
const int N = 1010, MOD = 1e9 + 7;
int f[N][N], n;
int solution_one() {
// initial
memset(f, 0, sizeof f);
f[0][0] = 1;
// dp loop
for (int i = 1; i <= n; i ++ ) {
for (int j = 1; j <= i; j ++ ) {
// 注意这 min(i - j, j)
f[i][j] = (f[i][j - 1] + f[i - j][min(i - j, j)]) % MOD;
}
}
return f[n][n];
}
int solution_two() {
// initial
memset(f, 0, sizeof f);
f[0][0] = 1;
// dp loop
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 res = 0;
for (int i = 1; i <= n; i ++ ) {
res = (res + f[n][i]) % MOD;
}
res = (res % MOD + MOD) % MOD;
return res;
}
int main()
{
// input
scanf("%d", &n);
int res = solution_two();
// output
printf("%d\n", res);
return 0;
}