题目: 整数划分
题意:
一个正整数 n 可以表示成若干个正整数之和,形如:n=n1+n2+…+nk,其中 n1≥n2≥…≥nk,k≥1。
我们将这样的一种表示称为正整数 n 的一种划分。
现在给定一个正整数 n,请你求出 n 共有多少种不同的划分方法。
输入格式
共一行,包含一个整数 n。
输出格式
共一行,包含一个整数,表示总划分数量。
由于答案可能很大,输出结果请对 10^9+7 取模。
数据范围
1 ≤ n ≤ 1000
输入样例:
5
输出样例:
7
解法一:视为完全背包问题
将给定的数视为背包的体积,那么题目就转变为了给我们n个体积和价值均为从1到n的物品,要求我们可以刚好将背包填满,问有多少种填法。
原本的完全背包是将集合中的所有方案比较出一个最大值,状态转移方程:f[j]=max(f[j],f[j-v[i]]+w[i]);
现在则是将完全背包集合中所有方案加在一起,状态转移方程:f[j]=f[j]+f[j-i];
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010,M=1e9+7;
int f[N];
int n;
int main()
{
cin>>n;
//当n==0时,只有一种选法,就是什么也不选
f[0]=1;
for(int i=1;i<=n;++i)
{
//完全背包容易被忽略的一点是j的起始值要注意,通常是当前物品的体积
for(int j=i;j<=n;++j)
f[j]=(f[j]+f[j-i])%M;
}
cout<<f[n];
return 0;
}
解法二:其它解法
将f[i][j]的集合视为两个子集,分别是方案中含有数字的最小值是1和方案中所含数字最小值大于1两个。
状态转移方程就是两者之和:f[i][j]=f[i-1][j-1]+f[i-j][j];
关键点就在于不重不漏的分割集合
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010,M=1e9+7;
//f[i][j]表示所有总和是i,且刚好有j个数的方案
int f[N][N];
int n;
int main()
{
cin>>n;
f[0][0]=1;
for(int i=1;i<=n;++i)
{
for(int j=1;j<=n;++j)
if(i>=j)
f[i][j]=(f[i-1][j-1]+f[i-j][j])%M;
}
int res=0;
for(int i=1;i<=n;++i)
res=(res+f[n][i])%M;
cout<<res;
return 0;
}