整数划分
一个正整数
n
n
n可以表示成若干个正整数之和,形如:
n
=
n
1
+
n
2
+
…
+
n
k
n=n1+n2+…+nk
n=n1+n2+…+nk,其中
n
1
≥
n
2
≥
…
≥
n
k
,
k
≥
1
n1≥n2≥…≥nk,k≥1
n1≥n2≥…≥nk,k≥1。
我们将这样的一种表示称为正整数
n
n
n的一种划分。
现在给定一个正整数
n
n
n,请你求出
n
n
n共有多少种不同的划分方法。
输入格式
共一行,包含一个整数
n
n
n。
输出格式
共一行,包含一个整数,表示总划分数量。
由于答案可能很大,输出结果请对
1
0
9
+
7
10^9+7
109+7取模。
数据范围
1
≤
n
≤
1000
1≤n≤1000
1≤n≤1000
输入样例
5
输出样例
7
解决思路
- 完全背包角度
由于对于一个数字
n
n
n,我们可以从
1
∼
n
1 \sim n
1∼n中选择数才对其进行表示,每个数的选择个数不限,我们将每个数看作一个物品,数值视作每个物品的重量,数字
n
n
n作为背包容量,那么就可以采用背包问题的角度解决这道题。
集合状态:
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示从
1
∼
i
1 \sim i
1∼i中的数选择,最后恰好是
j
j
j的方案数
集合属性:方案数的和
集合划分:以每个数选择几次作为划分依据(类比于每个物品选择几次)
状态转移:
d
p
[
j
]
=
d
p
[
j
]
+
d
p
[
j
−
i
]
dp[j] = dp[j] + dp[j - i]
dp[j]=dp[j]+dp[j−i],可以参照完全背包问题的优化方式
- 第二种方案
集合状态:
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 表示总和是
i
i
i,恰好有
j
j
j个数的方案
集合属性:方案数的和
集合划分:这个有一些难想,我们用一个图来表示
我们以方案中最小数是否为
1
1
1作为划分依据,当方案中的最小值为
1
1
1,那么将每个方案的
1
1
1减去,就会得到总和是
i
−
1
i-1
i−1,恰好有
j
j
j个数的所有方案。当方案中的最小值不是
1
1
1,那么将方案中的每个数都减去
1
1
1,就会得到总和是
i
−
j
i-j
i−j,恰好有
j
j
j个数的所有方案。
状态表示:
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
−
1
]
+
d
p
[
i
−
j
]
[
j
]
dp[i][j] = dp[i-1][j-1] + dp[i-j][j]
dp[i][j]=dp[i−1][j−1]+dp[i−j][j]
最终我们将
d
p
[
n
]
[
1
]
+
d
p
[
n
]
[
2
]
+
d
p
[
n
]
[
3
]
+
.
.
.
+
d
p
[
n
]
[
n
]
dp[n][1] + dp[n][2] + dp[n][3] + ... + dp[n][n]
dp[n][1]+dp[n][2]+dp[n][3]+...+dp[n][n]得到的就是答案。
代码实现
#include <iostream>
using namespace std;
const int mod = 1e9 + 7;
const int N = 1010;
int n;
int dp[N];
int main()
{
cin >> n;
dp[0] = 1;
for(int i = 1; i <= n; i++)
{
for(int j = i; j <= n; j++)
{
dp[j] = (dp[j] + dp[j - i]) % mod;
}
}
cout << dp[n] << endl;
return 0;
}
#include <iostream>
using namespace std;
const int mod = 1e9 + 7;
const int N = 1010;
int n;
int dp[N][N];
int main()
{
cin >> n;
dp[0][0] = 1;
for(int i = 1; i <= n ;i++)
{
for(int j = 1; j <= i; j++)
{
dp[i][j] = (dp[i - 1][j - 1] + dp[i - j][j]) % mod;
}
}
int ans = 0;
for(int i = 1; i <= n; i++)
{
ans = (dp[n][i] + ans) % mod;
}
cout << ans << endl;
return 0;
}