数的划分(完全背包求方案数)

  • Post author:
  • Post category:其他


一个正整数可以划分为多个正整数的和,比如n=3时:

3;1+2;1+1+1;

共有三种划分方法。

给出一个正整数,问有多少种划分方法

先不着急归纳,我们先试着分析状态转移关系

用f[i][j]表示前i个数可以组合出j的方案数

本题一个重要的性质:每个数字都可以使用无限多次(这不就是完全背包吗,笑)

也就是说现在组成的数字j,可以不使用数字i,也可以在使用数字i后变成数字j,但是得保证数字j是大于等于i的

这样我们就可以写出状态转移方程:f[i][j] = f[i-1][j]+f[i][j-i]

这里的一个不好懂的地方就在于如何很好的实现“可以多次使用”这个性质

答案就是可以通过for循环枚举j的大小来实现,由于可以使用新的数字了,所以我们要从头开始枚举(当然为了使用数字i,j肯定要从i开始循环,真的是这样嘛?二维会出错,一维是对的)

也就是说只要可以把数字i放进去我们就加上这个方案,这不就实现了“多次使用”这个性质了吗?

但是我们还要注意一下临界情况,通过状态转移方程我们可以发现i最好从2开始循环,而且j-i很有可能等于0,所以我们要初始化这两种情况

当i等于1的时候不论组成什么数字方案数都只有一种情况,而通过观察状态转移方程可得f[i][0]是只能通过f[i][j-1]得到的,也就是说f[i][0]是一种合法状态,且不能叠加,那么就直接初始化为1

先来一个好懂的没有优化过的代码

​
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int m;
int f[1010][1010];
int main()
{
	cin>>m;
	for(int i=1;i<=m;i++) f[1][i] = 1;
	for(int i=1;i<=m;i++) f[i][0] = 1;
	
	for(int i=1;i<=m;i++)
		for(int j=1;j<=m;j++)
		{
			if(j<i) f[i][j] = f[i-1][j];
			else f[i][j] = f[i-1][j]+f[i][j-i];
		} 
			
	cout<<f[m][m];
	
	return 0;		
}

​

需要注意的是为什么非得分情况讨论?

那是因为到了新一行的时候第i行的所有数据都还为0,即使没法更新放入数字i的情况也不能让这一行前面几个数据保持为0啊,若是保持为0的话就会丢失数据而无法继承,导致结果数目变少

然后我们考虑优化的场合

是否可以优化?答案当然是可以的,由于我们每次更新数据都只会用到上一行的情况,因此我们可以巧妙的只维持一行数据,

新一行的数据或者继承原本的数据,或者在原本的数据上做更新

也就是说我们只用f[j]来表示数字组合为j的方案数有多少

状态转移方程是f[j] = f[j]或者f[j] = f[j]+f[j-i]

自然很容易想到当i>j的时候是没法用新数字i的,所以f[j] = f[j]就是没法更新嘛

另一方自然是i<=j的情况了

而且,我们通过观察状态转移方程发现临界状态的表示似乎更简单了,只需要f[0] = 1就可

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n;
int f[10010];
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-i];
			
	printf("%d",f[n]);
	return 0;		
} 

要加油哇!



版权声明:本文为qq_51537085原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。