分解因數 遞迴
胡來的題目。
【題目描述】
給出一個正整數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行,每行輸出對應一個輸入。輸出應是一個正整數,指明滿足要求的分解的種數。
【輸入樣例】
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;
}
}