整数划分计数问题

整数划分计数问题

整数划分的问题

输入一个整数尝试将这个整数划分为不同整数相加之和,
例如4可以划分为:
4=1+1+1+1
4=1+1+2
4=1+3
4=2+2
4=4
一共有5种划分方案
尝试编写代码输入一个n,按字典序升序输出所有的解

这道题是经典的递归问题我们可以枚举所有可能递归实现

C++代码如下:
#include<iostream>
#include<vector>
using namespace std;
int len = 1; //len为拆分的整数数量 
int n;  
int counts;  //用来记录解的数量     
vector<int>ans;//创建一个容器用来存数,也可以用大数组来实现
void dfs(int sum) //sum为当前数组里数的和初始为零 
{
	if(n==sum)   //递归出口,当sum和n相等时说明完成了一个划分便输出这个划分 
	{
		counts++;  //解法加1 
		cout<<n<<"="; 
		for(int i = 1;i < len; i++)
		{
			cout<<ans[i];
			if(i<len-1)
				cout<<"+";
		}
		cout<<endl;
	}
	else       //sum不等于n说明还不行还要继续加 
	{
		for(int i=ans[len-1];i<=n;i++) 
		{/*为了避免重复我们使得每次加进去的数不会比前面的数小 
		所以使得i从ans[len-1]即上一个数开始这也符合了按字典序
		的升序输出,但如果i是划分的第一个数它没有前面的数还需
		要单独考虑它,所以为了方便我们一号位不用将其赋为0或1皆可*/
			if(sum+i<=n)//判断当前和加上这个数后会不会超过n
			{
				ans[len]=i;//不会就将其记录 
				len++;	   //长度加1 
				dfs(sum+i);//继续递归当前和sum+i 
				len--;     //撤掉这个成功的继续循环尝试其他的保证每个可能都试到 
			}
		}
	}
}
int main()
{
	cin>>n;           //输入一个整数 
	ans.resize(n+1,1); //0号位不用所以多开一个空间并赋初值为1函数里会说明为什么不用 
	dfs(0);			 //开始递归 
	printf("\n一共有%d种划分!",counts); 
	return 0;
}
整数划分的计数问题

而现在有新的问题我们要求输入一个数就能给出这个数一共有多少种划分方案,输入有多组对于每一组数据给出一个解

对于这个问题我们可以直接将上述代码改成只计数的代码但是直接提交便会超时上述代码的优势在于它尝试了所有的可能他可以输出所有的划分但现在我们不需要输出每一个划分我们只需要计算它的划分方案就行。这时如果再用上面的办法只是计数就会出现很多的重复计算所以我们需要动态规划来解决这道题

思路

显然易见的是1只有1种划分,2有2种,3有3种。那么我们可不可以依靠他们推出后面的呢?我们用7来举例

7的划分一共有15种他们分别是:

7的15种划分

我们分析7的划分的构成发现一共有4种:1开头的,2开头的,3开头的和7本身去掉7本身不看其实就是小于等于7/2的每一个数开头的,而这些开头的数又有什么特点呢?我们发现

1开头的其实就是6的划分里面开头大于等于1的所有划分(其实就是6的划分)
6的划分
2开头的其实就是5的划分里面开头大于等于2的所有划分
5的划分
3开头的其实就是4的划分里面开头大于等于3的所有划分
4的划分
至此我们思考能不能写一个函数f(n,m)可以返回m当中所有大于等于n的划分数

代码实现如下
#include<stdio.h>
int a[101]; //我们计算1到100所有的数划分0号位不用 
int fun(int n,int num)//函数返回num中所有大于等于n的划分 
{
	if(n==1)//如果n为1的划就返回num所有的划分 
	{
		return a[num];
	}
	else if(n>num/2)//如果n超过了num的一半那一定就只有num本身这一种划分 
		return 1;
	else
	{/*其他情况下如6中所有大于等于3的划分
	   就是6中减去1,2开头的所有划分和原问题一样
	   就让6的划分减去5中大于等于1的划分,4中大于
	   等于2的划分递归调用即可*/
		int step=a[num];
		for(int i=1;i<n;i++)
		{
			step-=fun(i,num-i);
		}
		return step;
	}	
}
int main()
{
	a[1]=1;a[2]=2;a[3]=3;//将显然的三个赋值 
	for(int i = 4;i<101;i++) //从4开始循环计算 
	{ 
		for(int j =1;j <= i/2;j++)
		{//a[i]等于所有小于i/2 j里每一个i-j中大于等于j开头的数量 
			a[i]+=fun(j,i-j);/*例如7 就是6中大于等于1的,
							5中大于等于2的,4中大于等于3的之和 */
		}
		a[i]+=1;//最后加上i本身 
	}
	int n;
	while(~scanf("%d",&n))
	{
		printf("%d\n",a[n]);
	}
	return 0;
} 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值