整数划分问题

先说明一下问题,什么是整数划分?

  • n=m1+m2+...+mi; (其中mi为正整数,并且1 <= mi <= n),则{m1,m2,...,mi}为n的一个划分。
  • 如果{m1,m2,...,mi}中的最大值不超过m,即max(m1,m2,...,mi)<=m,则称它属于n的一个m划分。这里我们记n的m划分的个数为f(n,m);
  • 举个例子,当n=5时我们可以获得以下这几种划分(注意,例子中m>=5)

5 = 5 
   = 4 + 1 
   = 3 + 2 
   = 3 + 1 + 1 
   = 2 + 2 + 1 
   = 2 + 1 + 1 + 1 
   = 1 + 1 + 1 + 1 + 1

 这里f(n,m)有以下几种不同的情况:

1. f(1, m) = 1,m>=1

当n=1,无论m取值多少,都只有一种

2.f(n, 1) = 1, n>=1

当m=1,无论n取值多少,都只有一种

3.f(n, m) = f(n, n),m>=n

当m大于等于n时,因为加数不可能大于n,如f(3,5)=f(3,3)

4.f(n, n) = f(n, n-1) -1

正整数n的划分是由s=n的划分和s<=n-1的划分构成。例如f(6,6) = 1+f(6,5)。

5.f(n, m) = f(n, m-1) + f(n-m, m), n>m>1

正整数n的最大加数不大于m的划分,是由s=m的划分和s<=m-1的划分组成。

例如f(6, 4) = f(6, 3) + f(2, 4) = f(6, 3) + f(2, 2)

综合以上情况,我们可以看出,上面的结论具有递归定义特征,其中(1)和(2)属于回归条件,(3)和(4)属于特殊情况,将会转换为情况(5)。而情况(5)为通用情况,属于递推的方法,其本质主要是通过减小m以达到回归条件,从而解决问题。其递推表达式如下:

  • f(n, m)= 1; (n=1 or m=1)
  • f(n, m)=f(n, n); (n<m)
  • 1+ f(n, m-1); (n=m)
  • f(n-m,m)+f(n,m-1); (n>m)


#include <cstdio>
#include <iostream>
using namespace std;

int split(int n,int m)
{
	if(n==1||m==1)
	return 1;
	else if(n<m)
	return split(n,n);
	else if(n==m)
	return split(n,n-1)+1;
	else return split(n,m-1)+split(n-m,m);
}

int main()
{
	int n;
	while(scanf("%d",&n)!=EOF)
	{
		cout<<split(n,n)<<endl;
	}
	return 0;
}

//https://nanti.jisuanke.com/t/25093
/*
思路:
用f(n,m)表示把n分解成不多于m个正整数
那么有以下几种情况:
1.如果n或m等于1,则f(n,m)=1;
2.如果m>n,则f(n,m)=f(n,n);
3.如果m==n,则f(n,m)=f(n,m-1)+1;
4.一般情况,f(n,m)=f(n-m,m)+f(n,m-1);
*/

/* 递归超时
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
using namespace std;
typedef long long ll;

ll split(int n,int m)  
{  
	if(n==1||m==1)  
		return 1;  
	else if(n<m)  
		return split(n,n);  
	else if(n==m)  
		return split(n,n-1)+1;  
	else return split(n,m-1)+split(n-m,m);  
}

int main()  
{  
	int n,k;  
	while(scanf("%d%d",&n,&k)!=EOF)  
	{  
		printf("%lld\n",split(n,k));  
	}  
	return 0;  
}  
*/

//用动态规划复杂度明显低了很多
#include<iostream>
using namespace std;
long long a[400][400];//a[n][k]表示将数n,分成k份,每份可以是任意值
int main()
{
	long long n,k;
	cin>>n>>k;

	for (int i=1;i<=n;i++)
	{
		a[i][1]=1;//将i分成1份,只有一种分法
	}
	for (int j=1;j<=k;j++)
	{
		a[1][j]=1;//将1分成j,有一种
	}
	for (int i=1;i<=n;i++)
	{
		for (int j=1;j<=k;j++)
		{
			//当i<j时,因为此时最多分成i份,实际上相当于将i分成i份
			if(i<j)
				a[i][j]=a[i][i];
			//当i==j时,分两种情况,一种是每份分1,
			//只有一种分法,第二种至少有一份为0,此时相当于a[i][j-1]
			else if (i==j)
				a[i][j]=a[i][j-1]+1;
			//当m>n时,也分两种情况,一种是至少有一份为0,
			//相当于a[i][j-1],第二种,先将j分出来,然后将i-j再分成j份,此时相当于a[i-j][j];
			else if (i>j)
				a[i][j]=a[i-j][j]+a[i][j-1];
		}
	}
	cout<<a[n][k];
	system("pause");
	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值