AcWing 900. 整数划分
题目描述
一个正整数n可以表示成若干个正整数之和,形如:n=n1+n2+…+nk,其中n1≥n2≥…≥nk,k≥1。
我们将这样的一种表示称为正整数n的一种划分。
现在给定一个正整数n,请你求出n共有多少种不同的划分方法。
输入格式
共一行,包含一个整数n。
输出格式
共一行,包含一个整数,表示总划分数量。
由于答案可能很大,输出结果请对109+7取模。
数据范围
1≤n≤1000
输入样例:
5
输出样例:7
算法1:完全背包模型
分析
可以把题目抽象成:有1,2,... ,n共n种物品,每种物品有无限多个,然后现在有一个容量也为n的背包,问恰好把背包装满有多少种不同的方案。
用f[i][j]
表示前i
种物品,背包容量为j
的时候,将背包装满的不同方案数
那么根据第i件物品选择0件、1件、2件……可以得到递推关系: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-1][j-k*i]
同时需要满足 k*i <= j
代码
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
const int N = 1010;
const int mod = 1e9+7;
int n;
int f[N][N];
int main()
{
scanf("%d", &n);
// 初始化
f[0][0] = 1;
//
for(int i = 1; i <= n; i++) // 前i件物品
{
for(int j = 0; j <= n; j++) // 容量为j;一定从0开始
{
for(int k = 0; k * i <= j; k++) // 第i件物品选k件
{
f[i][j] = (f[i][j] + f[i-1][j-k*i]) % mod;
}
}
}
printf("%d\n", f[n][n]);
return 0;
}
时间复杂度
三重循环,\(O(n^3)\)
完全背包模型简化版
根据递推方程:
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-1][j-k*i]
同时需要满足 k*i <= j
f[i][j-i] = f[i-1][j-i] + f[i-1][j-2*i] + f[i-1][j-3*i] + f[i-1][j-3*i] + ... + f[i-1][j-k*i]
同时需要满足 k*i <= j - i
所以 f[i][j] = f[i][j-i] + f[i-1][j]
所以可以将 上面代码简化成如下模式
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
const int N = 1010;
const int mod = 1e9+7;
int n;
int f[N][N];
int main()
{
scanf("%d", &n);
// 初始化
f[0][0] = 1;
//
for(int i = 1; i <= n; i++) // 前i件物品
{
for(int j = 0; j <= n; j++) // 容量为j;一定从0开始
{
f[i][j] = f[i-1][j]%mod; //
if(j >= i) f[i][j] = max(f[i][j], f[i][j-i]%mod + f[i-1][j]%mod); // 这个其实就是 f[i][j] = (f[i][j] + f[i-1][j-k*i]) % mod;的另一个表示
}
}
printf("%d\n", f[n][n]);
return 0;
}
优化为了\(O(n^2)\)
继续优化成1维
算法2:计数dp
分析
用f[i][j]
表示所有总和是i,恰好表示成j个数的方案总数
根据这j个数的最小值是不是1分成两类
- 当这j个数的最小值为1:那么去掉一个1也同时去掉了一类数,所以
f[i][j] += f[i-1][j-1]
- 单这j个数的最小值大于1:那么每个数去掉1,总和去掉了j,数的总类数不变,所以
f[i][j] += f[i-j][j]
所以f[i][j] = f[i-1][j-1] + f[i-j][j]
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int mod = 1e9 + 7;
const int N = 1010;
int f[N][N];
int n;
int main()
{
scanf("%d", &n);
f[0][0] = 1; // 总和是0,恰好表示成0个数的方案数为1
for(int i = 1; i <= n; i++) // 总和为i
{
for(int j = 1; j <= i; j++) // 恰好表示成j个数,其中 j <= i,总和为i,最多表示成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;
cout << res << endl;
return 0;
}
时间复杂度
\(O(n^2)\)