将n个数分为m个数[动态规划][两种思路]

[声明]欢迎讨论,欢迎一起学习

题目:

将n(一个整数)划分成m个(大于等于1的整数)有多少种可能

例如:

5 2         out: 2 (2,3 ; 1,4);

100 33   out : 2625726

9 3         out:7

//相信大家在网上找也是可以找到很多这种算法的。不过我觉得他们都是大神了,所以他们交流的时候可能很多默认知道的东西,像我这种菜鸡都要想很久

//所以,我写了两个算法,一个是自己想的(比较容易看懂),另一个是看了网上很多贴子(很多代码,才慢慢看懂的)在这里也一并展示给大家;


[算法一]:

工具:三维数组 a[i][j][k]  //表示将i 分为j个数,第j个数(即以后)的下限为(大于等于)k

//输出表示 就是 a[n][m][1];

//想到用这个方法其实是很符合逻辑的;想要用到之前已经做好的数据降低时间复杂度,取号一种情况后,之后情况就必须大于等于这个下限

//将n个数(要是不隔开,看起来总让我想起数学分析....)分为m个,为了避免重复计算必须限制下限;

//边界条件是:

i < j*k : a[i][j][k] = 0;//要是最小的情况都放不了了,那么不是0是什么呢?

i == j*k : a[i][j][k] = 1;//要是最小的情况,只能每个都放一样的,不然就不能放进去,只有一种情况

j ==1 && i >= k : a[i][j][k] =1;//只分为一个数,只有一种情况,但必须要求 i >= k,否则的话也是为0的

//递推公式是:(前面都已经筛选了这么多种情况了)

只剩下这个

if i > j*k : a[i][j][k] = sum{a[i-t][j-1][t]} (t == k,k+1,...,i/j)

//这是关于第j个数的选择,它至少是k,由于定义,所以 t 从k开始遍历

//遍历结束的位置是 i/j同k的,这看上去是一个比较粗糙的限制。但是当(i-t) >= (j-1)*t  => i >=j*t => t <= i/j

//所以,这其实是一个刚刚好的限制(要是有大佬告诉一个更好的限制,请在评论区留言,万分感激!)



/*   <不懂再来看这个,大佬可以跳过这个>
递推公式:
if(i>j*k) a[i][j][k] = sum{a[i-t][j-1][t]}; t= k,k+1,...,i/j;
将i分为j个数下限为k,就是其实在遍历最小的那个数(t),t可以是k,k+1,k+2,...,floor(i/j); 
边界条件:(由于我们一开始就已经将所有的值设置为了0),所以只需要考虑为为非0的边界条件
但是实际上,前面初始化全为0,本来就考虑到为0就是边界条件的一种,所以也是要考虑的
只不过减少了代码书写方面的考虑 
那么非0的边界是什么呢?
首先j==1&& i>=k时为1,这个很明显,最小为1,必须有a[i][j][k] == 1;
其次,i==j*k时候 那么就是刚好只有一种情况 a[i][j][k] == 1; 
分两种情况讨论,i能被j整除时,k肯定要小于等于(i/j),因为k是下限,不然就会超过i
i不能被j整除时,k*j < i <(k+1)*j,所以,k最大也只能是(i/j),因此,k的数学表达为
k <= floor(i/j),其C++表达就是i/j; 
*/ 


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

int a[101][101][101];
void f(int n,int m){
	memset(a,0,sizeof(a));
	for (int i=1;i<=n;++i)
	for (int j=1;j<=m;++j)//其实j最大不能超过i,否者不能保证每个都是整数了 
	for (int k=1;k<=(i/j);++k){
		if (j==1&&i>=k){
			a[i][j][k] = 1;
		}else if(i == j*k){
			a[i][j][k] = 1;
		}else if (i > j*k){
			for (int t=k;t<=(i/j);++t){
				a[i][j][k] += a[i-t][j-1][t];
			}
		}
	}
	cout << a[n][m][1]<<endl;
}

int main(){
	int n,m;//当n==m==0时,结束 
	while(cin>>n>>m&&(n||m)){
		f(n,m);
	}
}

[算法二]://算是看了网上很多的算法,这里只是做一个解释

//网上关于这个的算法很多,我看了很多之后,自己按照某一种的思路自己打了一个

工具:a[1000][1000];//dp[i][j]是网上大佬们都喜欢用的,我是个菜鸡,我喜欢用简单点的。

a[i][j] 表示将i分为j个数;

//这时候,要是你还是用之前的思路,是不行的,容我细细解析大佬们的思维;

//大局观:首先,这j个数进行考察,首先,要么全都是大于等于2的,否则就有一个是1;

//假如有一个是1,那么就直接将这个数拿出去,就是a[i-1][j-1];

//假如说全都是大于等于2,那么就将每个都拿掉一层1,很明显,拿掉一层其实不影响数量的

//到这里,我们就证明了,为什么 a[i][j] =a[i-j][j]+a[i-1][j-1];

//因为,这里用了关于每个数一个整体的讨论,从而构建了递推公式

//边界条件:

j==1||i==j : a[i][j] = 1;

同时,当i<j a[i][j] =0;//这个通过不处理实现。


//将n个物体分为m个组合
#include<iostream>
#include<cstring> 
using namespace std;
int a[1000][1000];
//a[i][j]表示将i这么多的数,分为j个数,有多少种分法 
void f(int n,int m){
	memset(a,0,sizeof(a));
	//边界条件:(将i分为j个数)
	//j==1||i==j:a[i][j]==1;
	//if(i>j) a[i][j]= a[i-j][j]+a[i-1][j-1];
	//讨论的情况是这样的,假如至少存在有一个为1,否者就全都是大于等于二的;
	//那么,这时候,就将每个都有的那个1给去掉,剩下的数就再来分成j个有多少就是多少
	//如果至少存在有一个是1,那么就将那个去掉后,剩下的,有多少种可能就是有多少种可能;
	//相加起来就是我们想要知道的a[i][j] 
	for(int i=1;i<=n;++i){
		for (int j=1;j<=i;++j){//当j>i时,必须为0; 
			if (j==1||i==j)a[i][j]=1;
			else a[i][j] = a[i-j][j]+a[i-1][j-1];
		}
	}
	cout << a[n][m]<<endl;
}

int main(){
	int n,m;
	while (cin >>n>>m&&n&&m){
		f(n,m);
	}
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值