貝殼2018.10.15筆試 找尋序列
題目描述
n確認序列長度,m確定最後一個數。還有一個要求是(從提示可以明顯看出),每兩個數,後者要能整除前者,比如[1,3,3]中,【1,3】要3能整除1,【3,3】要3能整除3。
動態規劃
如果n=1那麼只有一種情況。
如果n>1,那麼有遞迴式,dp[n,m] = sum( dp[n-1][x] )。x為n所有可能的因數。
根據上圖來看,比如找【3,4】,4的因數有1,2,4,那麼就等於【2,1】【2,2】【2,4】的個數相加,如果看每個元素儲存的序列,那就是【2,1】【2,2】【2,4】裡面的各個序列後面再加個4。
解釋一下這個遞迴式:
1.第n個數字是m,第n-1個必須是m的因數x
2.已經確定了第n個數字,剩餘的前n-1個數的情況,已經被所有的dp[n-1][x]存起來了(因為找的是x,x又是m的因數,所以就可以滿足第n-1個是m的因數)
3.根據這樣分析遞推,那麼又可以得到第n-2個又是第n-1的因數。
#include <bits/stdc++.h>
using namespace std;
const int mod = 1e9 + 7;
typedef long long LL;
LL dp[1005][1005];
int main()
{
for (int i = 1; i < 1005; i++) dp[1][i] = 1;
for (int i = 2; i < 1005; i++) { // 長度N
for (int j = 1; j < 1005; j++) { // 尾值M
int tmp = sqrt(j);
for (int k = 1; k <= tmp; k++) {
if (j % k == 0) {
dp[i][j] += dp[i - 1][k];
dp[i][j] %= mod;
dp[i][j] += dp[i - 1][j / k];
dp[i][j] %= mod;
}
}
if (tmp * tmp == j) {
dp[i][j] -= dp[i - 1][tmp];
dp[i][j] %= mod;
}
}
}
int N, M;
while (cin >> N >> M) {
cout << dp[N][M] << endl;
}
return 0;
}
程式碼來自牛客網大佬@HanKin,先在此鳴謝。解釋一下那個平方根,因為一個數的因數是成對出現的,我們只需要求到1到平方根的因數就行,另一半因數用原數除以因數就能得到。比如10,平方根是3,遍歷1,2,3。1是因數,10/1也是因數;2是因數,10/2也是因數。3不是因數。
滾動陣列
修改成py程式碼,使用滾動陣列,降低空間複雜度。
import math
n,m = map(int,input().split())
mod = 10**9 + 7
dp = [[0]*(m+1),[1]*(m+1)]
for i in range(2,n+1):
lastrow = (i-1) % 2
row = i % 2
for j in range(1,m+1):
temp = int(math.sqrt(j))
dp[row][j] = 0
for k in range(1,temp+1):
if j%k == 0:
dp[row][j] += dp[lastrow][k]
dp[row][j] %= mod
dp[row][j] += dp[lastrow][j//k]
dp[row][j] %= mod
if temp*temp == j:
dp[row][j] -= dp[lastrow][temp]
dp[row][j] %= mod
print(dp[n%2][m])
可以發現數組是重複利用的,但必須是[[0]*(m+1),[1]*(m+1)]
,不能只是[1]*(m+1)
,因為如果這樣,算dp[n,m] = sum( dp[n-1][x] )時,較小的x對應的dp[n-1][x]
會被更新為dp[n][x]
,這樣等算到較大的x對應的dp[n][x]
時,需要用到較小的x對應的dp[n-1][x]
,但此時較小的x對應的dp[n-1][x]
已經被更新為dp[n][x]
,這樣算出來就不對了。