我们先来看看题目:
Erin买了不少鸡蛋,她发现一天吃不完这么多,于是决定把n个同样的鸡蛋放在m个同样的篮子里,允许有的篮子空着不放,请问共有多少种不同的放法呢?
注意:2,1,1和1,2,1 是同一种分法。Input 第一行是测试数据的数目t(0 <= t <= 20)。以下每行均包含二个整数m和n,以空格分开。1<=m,n<=10。
Output 对输入的每组数据m和n,用一行输出相应的结果。
例如:
Input:
4
3 8
4 7
2 4
4 2
Output:
10
11
3
2
(注意结尾有换行)
第一次接触这种题目,大家最头疼的应该是例如(1, 2, 1)和(1, 1, 2)一样的重复问题,如何解决重复问题,是该题的一大难点。
我说说我的思路。
根据题意我们可以大概分成三种情况:鸡蛋数目多于篮子数目,鸡蛋数目等于篮子数目,鸡蛋数目少于篮子数目。
首先,我们来看看最简单的一种情况:鸡蛋数目少于篮子数目。
很明显,鸡蛋少的时候就算每个篮子都放一个鸡蛋,必定会有篮子放不满,因此我们很容易就可以将这种情况简化成为:将n个鸡蛋放入m个篮子(n <m)。
然后,我们再来看看另外一种情况:鸡蛋数目多于篮子数目
鸡蛋数目多于篮子数目时,很显然每个篮子都可以放满,当然也可以只有一个篮子放满,但是再放鸡蛋的过程中,很容易发生重复的情况,这时候我们不妨这样思考:
假设共有M个篮子,N个鸡蛋。现在我们先假设M个篮子都放满且每个篮子都只放一个鸡蛋,放入鸡蛋后剩下的鸡蛋数目为N-M,再让剩下的N-M去放M个篮子,此时我们不继续往下讨论。再假设M-1个篮子都只放满一个鸡蛋,与前面同样的道理,我们可以得到让N-(M-1)个鸡蛋放M-1个篮子……这样一个个增加空篮子的数目直到非篮子数目为2或1。
为了更清楚地说明这种原理,我们设一个放鸡蛋函数为f(M, N) (M为篮子数目,N为鸡蛋数目),然后我们引用例子中M= 4, N= 7,我们就可以进行拆分:
f(4,7)= f(4, 3)+f(3, 4)+f(2, 5)+f(1, 6)
= [f(3, 3)]+[f(3, 1)+f(2, 2)+f(1, 3)]+[f(1, 4)+f(2, 3)]+[f(1, 6)]
=[f(3, 3)]+[f(1, 1)+f(2, 2)+f(1, 3)]+[f(1, 4)+f(2, 3)]+[f(1, 6)]
我们发现当篮子数目M= 2或M= 1时, 分类数目是显而易见的当M= 1时, f(1,N)= 1; 当M= 2时,f(2, N)= N/2(当N为奇数取整)(注意当篮子数目为2时要求两个篮子都放,不存在一个篮子不放的情况)。于是我们得到f(3, 1)= 1, f(2, 2)= 1, f(1, 3)= 1, f(1, 4)= 1, f(2, 3)= 1,f(1, 6)= 1。
我们现在还不知道f(3, 3)的值。
所以我们必须讨论最后一种情况:鸡蛋数目等于篮子数目。
如果按照之前的拆分方式,我们可以将f(3, 3)拆分为:
f(3, 3)= f(3, 0)+ f(2, 1)+ f(1, 2)= f(3, 0)+ f(1, 1)+ f(1, 2)
根据上述的方法我们很快就得到 f=f(2, 1)=f(1, 1)= 1, f(1, 2)= 1。但是我们不知道怎么求f(3, 0)。
为了搞清楚发f(3, 0)是什么,我们认真思考将3个鸡蛋放3个篮子的情况:3个篮子放都各放1个鸡蛋;1个篮子放1个鸡蛋,一个放2个,另一个不放;3个鸡蛋都放进1个鸡蛋,另外两个不放。我们发现1个篮子放1个鸡蛋,一个放2个,另一个不放的情况对应于f(2, 1)即f(1, 1),3个鸡蛋都放进1个鸡蛋,另外两个不放的情况对应于f(1, 2),剩下的3个篮子放都各放1个鸡蛋的情况只能对应于f(3, 0)。这样我们就知道f(3, 0)= 1。所以
f(3, 3)= f(3, 0)+ f(2, 1)+ f(1, 2)= f(3, 0)+ f(1, 1)+ f(1, 2)=1+1+1=3
类似我们试着去求f(4, 4),我们同样发现f(4, 0)= 1。所以根据不完全归纳,我们可以推出f(M, 0)= 1。
现在再回到之前的例子。
f(4,7)= f(4, 3)+f(3, 4)+f(2, 5)+f(1, 6)
= [f(3, 3)]+[f(3, 1)+f(2,2)+f(1, 3)]+[f(1, 4)+f(2, 3)]+[f(1, 6)]
=[f(3, 3)]+[f(1, 1)+f(2, 2)+f(1, 3)]+[f(1, 4)+f(2, 3)]+[f(1, 6)]
= 3+1+2+1+1+1+1+1=11
很明显这就是例子给出的结果。
如果我们将这种方法转化为代码时,我们要实现这种拆分,我们必须用到递归,注意写代码时将基本情况交代清楚,否则程序将出现错误。
我写的代码如下:
#include<stdio.h>
int main() {
int put(int basket, int egg);//引入f(M, N)函数
int number, i, egg[100], basket[100];
scanf("%d", &number);
for (i= 0; i< number; i++) {
scanf("%d", &basket[i]);
scanf("%d", &egg[i]);
}
for (i= 0; i< number; i++) {
printf("%d\n", put(basket[i], egg[i]));
}
}
int way= 0;
int put(int basket, int egg) {
int i;
int sum= 0;
if (basket<= egg) {
//鸡蛋数目多于篮子数目的情况
if (basket== 2) way= egg/2;
//f(2, N)情况
if (basket== 1) way= 1;
//f(1, N)情况
if (egg== 0) way= 1;
//f(M, 0)情况
else {
if (egg>= 0) {
for (i= basket; i>= 1; i--) {
sum+= put(i, egg- i);
}
way= 0;
way+= sum;
}//进行拆分并对递归结果进行累加
}
}
else if (basket> egg) put(egg, egg);
//鸡蛋数目少于篮子数目的情况
else {
//篮子数目等于鸡蛋数目的情况
sum= 0;
for (i= basket; i>= 1; i--) {
sum+= put(i, egg-i);
}
way= sum;
}
return way;//输出最终结果
}
另外附上标答的代码,用的就是我的方法,但标答的代码更简洁
#include<stdio.h>
int egg(int m, int n);
int main() {
int t;
// m for baskets, n for eggs
int m, n;
int result = 0;
scanf("%d", &t);
while (t--) {
scanf("%d %d", &m, &n);
result = egg(m , n);
printf("%d\n", result);
}
return 0;
}
int egg(int m, int n) {
if (m == 1 || n == 0) return 1;
if (n < 0) return 0;
return egg(m-1, n) + egg(m, n-m);
}