數字遊戲2

在做一道hihocoder上的題目的時候,開始的時候一時間沒有想出來這裏寫博客理一下思路。
題目內容:
小 Hi 有一個數字 k,小 Hi 可以對他進行多次變換:每次變換選擇 k 的一個大於 1 的約數 d,然後將 k 變成 k/d
現在小 Hi 想將一個數字變成 1,求操作的方案數。由於方案數可能過大,你只需要輸出方案數對 10的9次方+7 取模後的值。
例如對於k=10,有三種方案:10->5->1,10->1,10->2->1。
輸入: 一個正整數 k 1 ≤ k ≤ 106
輸出: 輸出將 k 變成 1 的操作序列的方案數
樣例輸入:
10
樣例輸出:
3

最開始看到題目的時候,覺得該題目中蘊含了一種遞歸的關係:
以10舉例
10->5->1
10->1
10->2->1
一個數k的操作序列方案數實際上等於 他的所有不大於本身的約數(這裏的約數包含1)的方案數相加,這裏存在一種遞歸子問題的結構
以10舉例的化:
10的操作方案數 =  2的操作方案數 + 5的操作方案數 + 1的操作方案數
詳細的描述:
10->[5->1]
10->[1]
10->[2->1]
用方括號括起來的可以看成是子問題,由於1的操作方案數爲12的操作方案數也爲13的操作方案數也爲1,所以10的操作方案數爲3
從上面的分析中,我們可以看出其中包含的子問題特性和遞推關係,所以可以採用動態規劃的方法來解決

實現方案1:

import java.util.Arrays;
int k = 10; //假設輸入的k是
long mod = 1000000007; //取餘數所用的模
long[] dp = new long[k + 1];
Arrays.fill(dp, 0);
dp[1] = 1;
for(int i = 1; i <= k; i++) {
    for(int num = 2 * i; num <= k; num += i) {  //求所有i的倍數,因爲這些數爲i的倍數,所以其方案數需要加上i的方案數
        dp[num] = (dp[num] + dp[i]) % mod;
    }
}
System.out.println(dp[k]);
3

實現方案2:

import java.util.Arrays;
int k = 10; //假設輸入的k是
long mod = 1000000007; //取餘數所用的模
long[] dp = new long[k + 1];
Arrays.fill(dp, 0);
dp[1] = 1;
for(int i = 2; i <= k; i++) {
    //該dp過程去尋找i的所有約數,將這些約數的對應的子問題的和加起來就是它本身的解
    for(int tmp = 1; tmp * tmp <= i; tmp += 1) {
        if(i % tmp != 0) continue; 
        if(i / tmp == tmp || tmp == 1) {
            dp[i] = (dp[i] + dp[tmp]) % mod;  // 如果約數的平方爲i 或tmp值爲1的時候,則只計算一次,注意該情況
        } else {
            dp[i] = (dp[i] + dp[tmp]) % mod;
            dp[i] = (dp[i] + dp[i / tmp]) % mod;
        }
    }
}
System.out.println(dp[k]);
3

上面兩種實現方案雖然都是動態規劃(dp),但是一個用當前值往後遞推,將當前知道的信息疊加都後面的待求項去,另一種方法是,在要求解某數的解決方案數的時候,去求它的所有約數(不包括本身),然後將他們加起來做自己的值。
明顯兩種方案都能解決問題,但是方案一的運行效率明顯要高很多,因爲方案一在循環的時候不會有無效的遞推,方案二中,在求取i的時候,對tmp值的嘗試會出現失敗的情況,使其算法複雜度高於方案一的。
但是兩種遞推的思維方式還是要掌握的,一種是從前往後判斷自己與誰有關係,將自己的信息使用到與自己有關的對象上,一種是從後往前去尋找自己與誰有關係,將他們的信息使用到自己上。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章