胡来的题目。
给出一个正整数aa,要求分解成若干个正整数的乘积,即a=a1×a2×a3×...×ana=a1×a2×a3×...×an,并且1<a1≤a2≤a3≤...≤an1<a1≤a2≤a3≤...≤an,问这样的分解的种数有多少。注意到a=aa=a也是一种分解。
第1行是测试数据的组数nn,后面跟着nn行输入。每组测试数据占11行,包括一个正整数a(1<a<32768)a(1<a<32768)。
nn行,每行输出对应一个输入。输出应是一个正整数,指明满足要求的分解的种数。
说实话,刚开始刷递归的题心里还真没谱,都不知如何下手,遇到题,当然主要是思路不清晰,陆续看了些动规递推递归的帖子,稍微有点数,但做到这题还是有点蒙,看了好一会答案的代码才稍微有点感觉。
下面是算个人对递归的总结吧,初入,比较浅,可以直接跳到代码后面开始对问题分析。
这题是我几天前做的,现在来写当时的疑惑可能记不太轻,这里总结下我对递归的看法吧,递归的思想在于对待问题,是一种自顶向下的解决思路,(也就是从f(n)来考虑问题然后往f(n-1)或者f(x)(x为每次递归需要求的值)去调用),与递推,动规相似都是将大问题,化成统一的子问题的解题模式去求解。
基于递归的特性(自己调用自己),我们在写递归时需注意要有推动/调用本身的函数(类似f(n-1))的存在的同时,(当然不可以含f(n)本身,这样就会无限调用下去)需有若干个临界条件。(这些要点仿佛很简单,但对于刚开始用递归来写解决问题的我时,就会忘,所以只有摔过跟头了,才能更加对问题对方法有更深刻的理解。)还有一点就是,得想清楚你所写的递归函数的作用,他是求什么,什么作用,返回值还是,直接输出。
关于上面递归函数中包含调用f(n)函数相同参数相同这样的情况,之所以会犯这样的错,主要还是,对思想的表述有问题,一般调用f(n),类似Fibonacci,f(n)=f(n-1)+f(n-2)这样的表达都是想将结果的答案赋给一个值然后return出去,(这里看来就很矛盾,当时确实思路很不清晰啊),正确的做法直接return 出表达式的结果,或者返回内部声明的变量。(想想为什么不是全局的?————全局的变量的值会在每次递归调用时发生改变,这样多次调用全局变量的值会混。而局部变量的话,每次调用后,其值会随着函数压进函数栈中(是这样表达吗,第一次这样表达有点别扭)。
#include<iostream> using namespace std; int a; int f(int m, int n) { for(int i = m; i <= n / i; ++i) if(n % i == 0){ a++; f(i, n/i); } } int main() { int n; cin >> n; while(n--){ a = 1; int x; cin >> x; f(2, x); cout << a << endl; } }开始想这道题时,想着是类似i = i~an, 通过i *f(j) == an,然后将所有f(j)的数量来总和,写完代码后发现可能碰巧对了样例,但怎么改发现于正解不符(所有我们始终需保持对自己写的学的东西负责和持怀疑的态度)。后来看了正解,发现,方法就已经错了。
正确方法应该是:i = 2~an,通过an%i ==0来判断i是不是an的因数,如果是,则由an/i作为参数进一步带入函数来判断直至an/i == i,(此时i为最后一个因数)。首先需知道f(m,n)的含义——求n中从m开始到n/m所有分解种数的和。而为什么每次只要满足上面的条件就算加一种情况呢?这不妨举个例子当n = 100时,2、50算一种情况,而50可以继续分为2、25,这就有了2、2、25,再继续分,且每次分都能成为其一种分解情况所以a++。
下面另一种写法,区别在于分解次数加一是在每种分解情况完成时进行的,加的顺序是从分解到的最小因子开始加起,如对n = 100时一次调用f(2, 50) ,然后f(2, 25) ,f(2, 5), f(5, 5) (每次调用到f(x, x),相当于一种情况,后面变成f(x,1)然后直接a++)则先是递归到2 2 5 5 a++ 后,回溯到f(2, 25), 再是f(25, 25)时相当于2 2 25的情况然后a++ ,然后在回溯到f(2, 50) , f(50, 50),a++,这样对i ~ n/i中的分解情况累加。
再对for循环中的循环条件进行分析,他不会出现对两个相同情况2 50 和 50 2重复a++, 当i = 50 时,调用f(50,2) (f(50, 100/2))时会直接不满足条件而退出。而还要设i<=n, 是为了保证在回溯过程中将f(25, 25) 这样的情况给加上。
而这种情况实现方法像上面提到的虽然不计算重复情况,但还是会执行判断,会更耗时。
#include<iostream> using namespace std; int a; void f(int m, int n) { if(n == 1) { a++; return ; } else for(int i = m; i <= n; ++i) if(n % i == 0){ f(i, n/i); } } int main() { int n; cin >> n; while(n--){ a = 0; int x; cin >> x; f(2, x); cout << a << endl; } }而下面这种实现更是递归最典型的用法,是自顶向下的递归实现思路。从x开始向1递归,对1到x的每位进行判断(是否是其最大因子)累加次数的同时,加上下次递归的f(a, b-1)的情况,f(a, b)中若a%b == 0 则,进一步递归分解。否者对b-1进行判断。b减到1时结束返回0(可以想象一个质数只有第一次加一,后面b一直在减一,直到b == 1则返回0),而被分解的数a等于1,意味着上次调用时的因数刚好将其整除尽,算作一种情况返回1。
#include<iostream> using namespace std; int count(int a, int b) { if(a == 1)//when a == b then a/b == 1 then sum++ return 1; if(b == 1)//border return 0; if(a % b == 0) return count(a/b, b) + count(a, b-1);//use f(a,b-1) to get cycle from n-1 to 2 else count(a, b-1); } int main() { int n; cin >> n; while(n--) { int x; cin >> x; cout << count(x, x) << endl; } }