题目链接:https://www.acwing.com/problem/content/description/902/
题目:
一个正整数 nn 可以表示成若干个正整数之和,形如:n=n1+n2+…+nk,其中 n1≥n2≥…≥nk。
我们将这样的一种表示称为正整数 n 的一种划分。
现在给定一个正整数 n,请你求出 n 共有多少种不同的划分方法。
输入格式
共一行,包含一个整数 n。
输出格式
共一行,包含一个整数,表示总划分数量。
由于答案可能很大,输出结果请对 1e9 + 7 取模。
数据范围
1≤n≤1000
输入样例:
5
输出样例:
7
分析1:
其中 n1≥n2≥…≥nk。也就是不考虑顺序。1 + 2 + 1 和 2 + 1 + 1为一种。
所以此题有两种方式。
方式1:计数类DP
方式2:完全背包问题
方式1:
f[i][j]状态表示:总和为i,由j个数组成的方案数。
状态计算:
所以f[i][j] = f[i - 1][j - 1] + f[i - j][j]
计数类方式的代码实现:
//方法1:计数类DP # include <iostream> using namespace std; const int N = 1010; const int MOD = 1e9 + 7; int f[N][N]; int a[N]; int n; int main() { scanf("%d",&n); for(int i = 1; i <= n ; i++) { scanf("%d",&a[i]); } f[0][0] = 1; //总和为0,由前0个数组成的方案为1.其他总和为0的方案为0 for(int i = 1; i <= n ; i++) { for(int j = 1 ; j <= i ; j++) // 数的个数一定不会超过i个 { 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; } printf("%d\n",res); return 0; }
方法2:完全背包问题
将这个问题看出背包容器为n,由1~n每个数可以取无限个去组成值为n的方案数。 所以为基本的完全背包问题。
分析:状态表示和状态计算
f[i][j]的状态表示:前i个数组成和为j的方案数
状态计算:
不要i:f[i - 1][j]
要i:
f[i - 1][j - i ] + f[i - 1][j - 2i] + ... + f[i - 1][j - k * i]
所以f[i][j] = f[i - 1][j] + f[i - 1][j - i] + f[i - 1][j - 2i]....+f[i - 1][j - ki]
进行优化:
f[i - 1][j - i] = f[i - 1][j - i] + f[i - 1][j - 2i] + ... f[i - 1][j - ki]
所以f[i][j] = f[i - 1][j] + f[i][j - i]
完全背包方式的代码实现:
朴素做法:
//方法2:完全背包问题:
# include <iostream>
using namespace std;
const int N = 1010;
const int MOD = 1e9 + 7;
int f[N][N];
int a[N];
int n;
int main()
{
scanf("%d",&n);
f[0][0] = 1;
for(int i = 1 ; i <= n ; i++)
{
for(int j = 0 ; j <= n ; j++)
{
for(int k = 0 ; k * i <= j ; k++)
{
f[i][j] = (f[i][j] + f[i - 1][j - k * i]) % MOD;
}
}
}
/*
for(int i = 1 ; i <= n ; i++)
{
for(int j = 1 ; j <= n ; j++)
{
printf("%d ",f[i][j]);
}
printf("\n");
}
*/
printf("%d",f[n][n]);
return 0;
}
优化掉一个for()循环后:
//方法2:完全背包问题:
# include <iostream>
using namespace std;
const int N = 1010;
const int MOD = 1e9 + 7;
int f[N][N];
int a[N];
int n;
int main()
{
scanf("%d",&n);
f[0][0] = 1;
for(int i = 1 ; i <= n ; i++)
{
for(int j = 0 ; j <= n ; j++)
{
f[i][j] = f[i-1][j] % MOD; // 为什么优化为一维空间的时候不需要这句话了呢?这是因为变为一维的时候其实就变成了f[j] = f[j]了,所以可以省略,但是二维的时候不能省略,所以要注意一下。完全背包的一维空间,二维空间模板的区别。
if(j >= i)
{
f[i][j] = (f[i][j] + f[i][j - i]) % MOD;
}
}
}
/*
for(int i = 1 ; i <= n ; i++)
{
for(int j = 1 ; j <= n ; j++)
{
printf("%d ",f[i][j]);
}
printf("\n");
}*/
printf("%d",f[n][n]);
return 0;
}
最后在优化空间:
//方法2:完全背包问题: # include <iostream> using namespace std; const int N = 1010; const int MOD = 1e9 + 7; int f[N]; int a[N]; int n; int main() { scanf("%d",&n); f[0] = 1; for(int i = 1 ; i <= n ; i++) { for(int j = 0 ; j <= n ; j++) { f[j] = f[j] % MOD; // 实际是这样的,但是这样的形式这一行可以省略,并且j可以直接从i开始遍历。 if(j >= i) { f[j] = (f[j] + f[j - i]) % MOD; } } } /* for(int i = 1 ; i <= n ; i++) { for(int j = 1 ; j <= n ; j++) { printf("%d ",f[i][j]); } printf("\n"); }*/ printf("%d",f[n]); return 0; }