先注明学习博客的地址:(http://www.cnblogs.com/hoodlum1980/archive/2008/10/11/1308493.html)
题目描述:任何正整数n都可以写成n=n1+n2+n3+……+nk;1<=n1,n2,n3,……nk<=n;这被称为整数n的划分。
例如正整数6的划分如下:
6;
5+1;
4+2,4+1+1;
3+3,3+2+1,3+1+1+1;
2+2+2,2+2+1+1,2+1+1+1+1;
1+1+1+1+1+1。
目标:求出n的划分个数,6的划分个数为11。
该博主给出了两种做法,我先研究一下第一种做法(递归法)
首先引入另一个概念:n的m划分,就是说划分必须满足:1<=n1,n2,n3,……nk<=m;这时我们就可以进行递归的分析了,求n的划分就是求f(n,n);
下面讨论f(n,m)的求法:
1.当n等于1时,只有一种划分;
2.当m等于1时,也只有一种划分;
3.当n=m时,
划分中含有m时,有一种划分;
划分中不含m时,有f(n,m-1)中划分;
综上:f(n,m)=1+f(n,m-1);
4.当n>m时,
划分中含有m时,即划分的形式是{m,{x1, x2, ..., xi}},则{x1, x2, ..., xi}个数为f(n-m,m);
划分中不含m时,即划分中的数都比m小,则划分个数为f(n,m-1);
综上划分f(n,m) = f(n-m,m)+f(n,m-1);
通过对求f(n,m)的过程分析,可见可能出现n<m的情况。
5.当n<m时,f(n,m)= f(n,n);
综上:
f(n, m) = 1; ( n = 1 or m = 1 )
f(n, n); ( n < m )
1+ f(n, m - 1); ( n = m )
f(n - m, m) + f(n, m - 1); ( n > m )
在写代码AC掉那道题之前,先体会一下,这个递归为什么能成为递归的经典例题吧。其实这题并不能很明显的找到缩小问题规模的入口,反而让你引入了一个过渡的变量,而且每一步的缩小都很勉强,只能从表达式上看出来。我之所以觉得这道题很好的原因是因为这种分类讨论是高中时经常用的,或许这才是活学活用的实例,是一种考虑问题的方式,是一种生活的态度,是智商带动情商的提高(扯远了&……&……&)。
正经事AC代码:
1 #include<stdio.h> 2 3 int F(int n, int m) 4 { 5 if (n == 1) 6 return 1; 7 if (m == 1) 8 return 1; 9 if (n < m) 10 return F(n, n); 11 if (n == m) 12 return F(n, m - 1) + 1; 13 if (n>m) 14 return F(n - m,m) + F(n, m - 1); 15 } 16 17 18 int main() 19 { 20 int T; 21 scanf("%d",&T); 22 while (T--) 23 { 24 int n; 25 scanf("%d",&n); 26 printf("%d ",F(n,n)); 27 } 28 }
这里其实还是有一个问题的就是是否有必要加上记忆化搜索,经过测试发现是很有必要加的,测试的代码:
1 #include<stdio.h> 2 #include<string.h> 3 4 int a[100][100]; 5 6 int F(int n, int m) 7 { 8 if (a[n][m] > 0){ 9 printf("Ues the a[%d][%d] ",n,m); 10 return a[n][m]; 11 } 12 if (n == 1) 13 return a[1][m]=1; 14 if (m == 1) 15 return a[n][1]=1; 16 if (n < m) 17 return a[n][n]=F(n, n); 18 if (n == m) 19 return a[n][m]=F(n, m - 1) + 1; 20 if (n>m) 21 return a[n][m]=F(n - m,m) + F(n, m - 1); 22 } 23 24 25 int main() 26 { 27 int T; 28 scanf("%d",&T); 29 while (T--) 30 { 31 memset(a,-1,sizeof(a)); 32 int n; 33 scanf("%d",&n); 34 printf("%d ",F(n,n)); 35 } 36 }
好了,这题就到这儿了,至于博主提供了另一个方法,暂时就不讨论了。