整数划分


题目链接:https://www.acwing.com/problem/content/description/902/

题目描述

一个正整数 n 可以表示成若干个正整数之和,形如:n=n1+n2+…+nk,其中 n1≥n2≥…≥nk,k≥1。
我们将这样的一种表示称为正整数 n 的一种划分。
现在给定一个正整数 n,请你求出 n 共有多少种不同的划分方法。

输入描述

共一行,包含一个整数 n。

输出描述

共一行,包含一个整数,表示总划分数量。
由于答案可能很大,输出结果请对 109+7 取模。
1≤n≤1000

示例

输入

5

输出

7

借鉴完全背包问题求解

切入点

通过分析整数划分这个问题,我们可以发现它与完全背包问题具有一定的相似性。待划分的整数相当于背包的容量,要划分成的数字相当于要装入背包的物品。因此,我们就会想到化用完全背包问题的状态转移方程。

状态表示

f(i,j)表示满足如下条件划分方案的数量:

  • 划分成的数字小于等于i
  • 划分后的数字的总和等于j

注意于完全背包的不同,此处的f(i,j)表示的是方案数不是最大值。

状态划分与计算

与完全背包相同,对于f(i,j)我们划分如下:

  • 用了0个i,方案数即f(i-1,j)
  • 用了1个i,方案数即f(i-1,j-i)
  • 用了2个i,方案数即f(i-1,j-2*i)
    ·········

因此,初步的状态转移方程为:
f(i,j)=f(i-1,j)+f(i-1,j-i)+··· (j-k*i>=0)
在完全背包问题中,我们通过等价变形状态转移方程进行了优化,此处我们也进行尝试:
f(i,j)=f(i-1,j)+f(i-1,j-i)+f(i-1,j-2*i)···
f(i,j-i)=f(i-1,j-i)+f(i-1,j-2*i)+···
因此,优化后的状态转移方程为:
f(i,j)=f(i-1,j)+f(i,j-i)
最后,与完全背包一样,可以将f(i,j)降为一维优化,不再赘述,AC代码为一维版本。

初始边界处理

显然:
f(i,0)=0
f(0,i)=0
考虑一下f(0,0),不划分与之对应,f(0,0)=1,只要从f(i,j)的意义上入手,所有f(i,j)保持一致,应该就能确定正确的初始值。
如果感觉不保险,就用边界值代进去试试,在这里就用f(1,1),f(1,1)算出来对就行。

另一种思路

状态表示

f(i,j)为满足如下的条件的划分方案数:

  • 划分成的数字总和为i
  • 划分成的数字的个数为j

    状态划分与计算

    对于f(i,j)所代表的状态,我们进行如下划分:
  • 方案中所划分成的最小数字是1
  • 方案中所划分的最小数字大于1

记划分一的方案数为x,划分二的方案数为y。
我们将划分一中每个方案的那个1去掉,得到的新方案与原方案数量一致,将新方案集合与f(i-1,j-1)对照。首先,划分一中的方案将变成和为i-1,共j-1项,符合f(i-1,j-1)的状态要求,所以x<=f(i-1,j-1)。再将f(i-1,j-1)所代表的方案均加上一个1,将变成和为i,共j项,符合划分一,即f(i-1,j-1)<=x。所以,x=f(i-1,j-1)。
同理,我们将划分二中每一项减去1,将新方案集合与f(i-j,j)对照。可以得到y=f(i-j,j),证明方法同上,不再赘述。
综上,状态转移方程为:
f(i,j)=f(i-1,j-1)+f(i-j,j)
另外,这个无法优化成一维,因为得到f(i,j)需要上面几行的数据,必须存储下来以供计算。

完全背包思路的AC代码


#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1001;
const int mod = 1e9 + 7;

int n;
int f[N];

int main()
{
	cin >> n;
	f[0]=1;
	for (int i = 1; i < N; i++)
		for (int j = i; j <= n; j++)
			f[j] = (f[j] + f[j-i]) % mod;
	cout << f[n] << endl;
	return 0;
}

非完全背包思路的AC代码


#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1001;
const int mod = 1e9 + 7;

int n;
int f[N][N]; //边界处理 自动初始化为0 没写代码但要知道应该初始化

int main()
{
	cin >> n;
	f[0][0] = 1; //边界处理
	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;
	cout << res << endl;
	return 0;
}

文章作者: Kong Aobo
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Kong Aobo !
  目录