放苹果

题目描述

把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?(用K表示)5,1,1和1,5,1 是同一种分法。

输入

第一行是测试数据的数目t(0≤t≤20)。以下每行均包含二个整数M和N,以空格分开。1≤M,N≤400。

输出

对输入的每组数据M和N,用一行输出相应的K。

样例输入

1
7 3

样例输出

8

算法分析

样例分析

苹果和盘子都是不加区分的,当有 7个苹果3个盘子时,总有8种不同的放置方案

//一个盘子
7
//两个盘子
6+1
2+5
3+4
//三个盘子
1+1+5
1+2+4
1+3+3
2+2+3

讨论初始项

F ( i , j ) F(i,j) F(i,j)为将 i i i个苹果放到 j j j个盘子的方案数.
当只有一个苹果或只有一个盘子时,只有一种方案数

F(i,j)=1   (i=1 or j=1 )
F(i,j)=1   (i=0 )//只有0个苹果时,方案数也为1,具体看下面情况2的分析

分类讨论可能出现的情况

情况1:苹果比盘子少
情况2:苹果大于等于盘子的数量
F ( i , j ) F(i,j) F(i,j)为将 i i i个苹果放到 j j j个盘子里的方案总数
对于情况1:假如求3个苹果放入5个盘子的方案数,不管怎么放,至少会剩下2个空盘子,因为苹果和盘子是不加以区分的,剩下的2个空盘子也是不加以区分的,上述的问题就变成了求将3个苹果放个3个盘子的方案数,所以可得:

F(i,j)=F(i,i)  (i<j)

讨论情况2:苹果大于等于盘子的数量

根据样例分析我们可以得知,在求三个盘子的方案的时候,其中又包含了两个盘子的方案数,所以可以通过某种方式,找出“嵌套”在其中的子问题(问题类型相同但规划更小)。

3+4和1+2+4
2+5和1+1+5

对于情况2—拆分问题

在拆分问题时,我们要保证拆分后的各个子问题之间是互斥(递推中),并且各个子问题的方案总和刚好是当前问题方案数,如果各个子问题之间有相交的部分,在计算方案数量时则需要把相交的部分的方案数减去。

当前问题可以拆分成两个子问题:

子问题1:把当前所有盘子的填满
子问题2:还剩一个盘子没有填满

F ( i , j ) F(i,j) F(i,j)为将 i i i个苹果放到 j j j个盘子的方案数,

对于子问题1

所有的盘子都不为空,每个盘子至少放一个苹果,相当于把 ( i − j ) (i-j) (ij)个盘子放到 j j j个盘子的方案数量,即 F ( i − j , j ) F(i-j,j) F(ij,j)

用比如求7个苹果放3个盘子的且盘子不为空方案数,
第一步:每个盘子都放一个苹果,方案数量为1(已满足每个盘子不为空的条件)
第二步:将剩下的4个苹果放入3个盘子里面,方案数为a(这时盘子可以为空)
根据乘法原理:方案数为1*a=a,
求7个苹果放3个盘子的且盘子不为空方案数相当于把4个苹果放3个盘子的方案数。

i i i为0时,也是要保存初始项的,例 F ( 2 , 2 ) = F ( 0 , 2 ) + F ( 2 , 1 ) = 1 + 1 + 2 F(2,2)=F(0,2)+F(2,1)=1+1+2 F(2,2)=F(0,2)+F(2,1)=1+1+2.

对于子问题2

有一个盘子为空,问题变为求将i个苹果放到(j-1)个盘子里面的方案数,即 F ( i , j − 1 ) F(i,j-1) F(i,j1)

显然,子问题1和子问题2是互斥的,并且子问题1加上子问题2的方案数为原问题的总方案数。递推公式为:

F(i,j)=F(i-j,j)+F(i,j-1)  (i>=j) 

注: F ( i , j − 1 ) F(i,j-1) F(i,j1)表示空一个盘子的方案数,如果继续 F ( i , j − 1 ) F(i,j-1) F(i,j1)这个问题拆分,拆分后的子问题包含了空两个盘子的方案数,也就是 F ( i , j − 1 ) F(i,j-1) F(i,j1)包含了 F ( i , j − 2 ) F(i,j-2) F(i,j2)

汇总

F(i,j)=F(i,i)   i<j
F(i,j)=1   i=1 or j=1 or i=0 
F(i,j)=F(i,i)   i<j
F(i,j)=F(i-j,j)+F(i,j-1)  i≥j 

代码实现

代码1:递归实现(33分)
#include <iostream>
using namespace std;

long long fun(int n,int m){
	if(n==1 || n==0||m==1) return 1;
	else if(n<m) return fun(n,n);
	else return fun(n-m,m)+fun(n,m-1);
} 
int main(){
	int n,m,t;
	cin>>t;
	while(t--){
		cin>>n>>m;
		cout<<fun(n,m)<<endl;
	}
	
	return 0;
} 

代码2:递归实现+优化(100分)

上面的代码1有很多子问题重复求解,我们可以用一个二维数组保存子问题的解,如果当前的子问题已经解求过了,则直接返回,这种方法称为“记忆化搜索”。
需要注意的是,每次计算数组的值都需要重置为0,以便下一次使用,初始化可以使用 memset(f,0,sizeof(f)); 语句。

#include <iostream>
using namespace std;
const int MAXN=401;
long long f[MAXN][MAXN]={0};
long long fun(int n,int m){
	if(n==1 || n==0||m==1) return 1;
	if(f[n][m]>0) return f[n][m];//如果当前子问题已经有解,直接返回
	else if(n<m) return f[n][m]=fun(n,n);
	else return f[n][m]=fun(n-m,m)+fun(n,m-1);
} 
int main(){
	int n,m,t;
	cin>>t;
	while(t--){
	  	for(int i=0;i<n;i++)
	  		for(int j=0;j<m;j++)
	  			f[i][j]=0;
	  	
		cin>>n>>m;
		cout<<fun(n,m)<<endl;
	}
	return 0;
} 

代码3:递推(100分)

使用迭代(递推)的方式求解,苹果数作为外层循环(从0开始循环),盘子数作为内层循环,这样就能保证每次求解时其依赖的子问题已经有解了。

#include <iostream>
#include <cstring> 
using namespace std;
const int MAXN=401;
long long f[MAXN][MAXN]={0};

int main(){
	int n,m,t;
	cin>>t;
	while(t--){
		memset(f,0,sizeof(f));//将f数值的值全部初始化为0; 
		cin>>n>>m;
		for(int i=0;i<=n;i++)
			for(int j=1;j<=m;j++)
				if(i==1||i==0||j==1) f[i][j]=1;
				else if(i<j) f[i][j]=f[i][i];
				else f[i][j]=f[i-j][j]+f[i][j-1];
			 
		cout<<f[n][m]<<endl;
	}
	return 0;
} 
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值