題目描述
給定一根 \(1\) 到 \(N\) 的數軸。一開始有一個棋子在 \(N\)。每次棋子 \(x\) 可以跳到 \(x-1,x+1\) 或 \(x\) 的因子處(不能超出 \(1\) 到 \(N\))。
每個點只能到達一次。求棋子到達 \(1\) 的方案數。
思路
由於求倍數比因子簡單,所以把問題變成從 \(1\) 到 \(N\),每次跳倍數。
我們可以發現,棋子的行走路徑由兩種型別的路拼在一起:
由於有先跳倍數再 \(-1\) 的跳法,此時跳的倍數必須大於走過的最遠的位置,所以狀態中要記錄最遠走到哪裡。
令 \(dp_{i,j}\) 表示當前在 \(i\),最遠走到了 \(j\) 的方案數。
當 \(i=j\) 時,我們有轉移 \(dp_{i+1,i+1}\leftarrow dp_{i,j}\)。
當然我們也可以跳倍數,也就是對於每個 \(k=i\cdot m(m>1)\),那麼都有轉移 \(dp_{x,k}\leftarrow dp_{i,j}(j<x\le k)\)。
而這種轉移可以使用字首和維護。
空間複雜度 \(O(N^2)\),時間複雜度 \(O(N^2\log N)\)。
程式碼
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5005;
int n, MOD, dp[MAXN][MAXN], sum[MAXN][MAXN];
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> MOD;
dp[1][1] = 1;
for(int i = 1; i <= n; ++i) {
for(int j = i; j <= n; ++j) {
sum[j][i] = sum[j][i] + sum[j][i - 1] - (sum[j][i] + sum[j][i - 1] >= MOD ? MOD : 0);
//cout << sum[j][i] << " \n"[j == n];
dp[i][j] = dp[i][j] + sum[j][i] - (dp[i][j] + sum[j][i] >= MOD ? MOD : 0);
//cout << dp[i][j] << " \n"[j == n];
if(i == j) {
dp[i + 1][max(i + 1, j)] = dp[i + 1][max(i + 1, j)] + dp[i][j] - (dp[i + 1][max(i + 1, j)] + dp[i][j] >= MOD ? MOD : 0);
}
for(int k = 2 * i; k <= n; k += i) {
if(k > j) {
sum[k][j + 1] = sum[k][j + 1] + dp[i][j] - (sum[k][j + 1] + dp[i][j] >= MOD ? MOD : 0);
}
}
}
}
cout << dp[n][n];
return 0;
}