题目为:POJ1664
思路:
使用函数apple(m,n)表示将m个苹果放入n个盘子。
首先考虑问题边界(递归出口): 当没有苹果或者只有一个盘子的时候,只有一种摆放方法所以apple(0,n)=apple(m,1)=1;
然后考虑一般关系式:
①当苹果数量<盘子数量时问题可以转化为将m个苹果放入m个盘子中,即apple(m,n)=apple(m,m);
②当苹果数量>=盘子数量时,问题可以分解为两个子问题:所有盘子都有苹果or有盘子为空。前者即先将所有盘子放入一个苹果再分剩下的苹果到n个盘子中;后者可以表示为将m个苹果放入n-1个盘子中。即apple(m,n)=apple(m,n-1)+apple(m-n,n)。
(理解点: 有盘子为空只放出来了一个空盘子,但实际上apple(m,n-1)这个子问题也有其“有盘子为空”的子问题,这样一直细分子问题就解决有多个空盘子的问题。)
那么,其中我们将递归问题分解为比它小的子问题时,需要考虑到每一种子问题的情况,不能漏掉任何一种子问题,这就有点像高中我们写“排列组合”数学题时需要考虑的情景。
AC代码如下:
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
typedef long long ll;
ll apple(ll m,ll n){
if(m==0)
return 1;
if(n==1)
return 1;
if(n>m)
return apple(m,m);
else{
return apple(m-n,n)+apple(m,n-1);
}
}
int main(){
ll t;
cin>>t;
while(t--){
ll m,n;
cin>>m>>n;
ll ans=apple(m,n);
cout<<ans<<endl;
}
}
附:较为官方的递归定义
程序调用自身的编程技巧称为递归。
它通常把一个大型的复杂问题转化为一个与原问题相似的规模较小的问题来解决,可以极大的减少代码量。 使用递归要注意的4条基本法则:
1)基本情形:必须总要有某些基准情形,它无需递归就能解出。
2)要有进展:对于那些需要递归求解的情形,每一次递归调用都必须要使状态朝向一种基本情形推进。
3)设计法则:假设所有的递归调用都能运行。
4)合成效益法则:在求解一个问题的同一实例时,切勿在不同的递归调用中做重复性的工作。
最后一点涉及到记忆化递归,将已经算过的东西记录下来,避免重复计算,和搜索中的记忆化剪枝思想类似。