胡来的题目。
【题目描述】
给出一个正整数a,要求分解成若干个正整数的乘积,即a=a1×a2×a3×...×an,并且1<a1≤a2≤a3≤...≤an,问这样的分解的种数有多少。注意到a=a也是一种分解。
【输入】
第1行是测试数据的组数nn,后面跟着nn行输入。每组测试数据占1行,包括一个正整数a(1<a<32768)。
【输出】
n行,每行输出对应一个输入。输出应是一个正整数,指明满足要求的分解的种数。
【输入样例】
2
2
20
【输出样例】
1
4
说实话,刚开始刷递归的题心里还真没谱,都不知如何下手,遇到题,当然主要是思路不清晰,陆续看了些动规递推递归的帖子,稍微有点数,但做到这题还是有点蒙,看了好一会答案的代码才稍微有点感觉。
下面是算个人对递归的总结吧,初入,比较浅,可以直接跳到代码后面开始对问题分析。
这题是我几天前做的,现在来写当时的疑惑可能记不太轻,这里总结下我对递归的看法吧,递归的思想在于对待问题,是一种自顶向下的解决思路,(也就是从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;
}
}