题意
你有两个个整数 n ( 2 ≤ n ≤ 2 ∗ 1 0 5 ) , m ( 1 0 8 < m < 1 0 9 ) n(2 \le n \le 2*10^5),m(10^8<m<10^9) n(2≤n≤2∗105),m(108<m<109), m m m为素数
可以进行两种操作
- 选择一个数 y ( 1 ≤ y < x ) y (1 \le y < x) y(1≤y<x),将 x x x 变为 x − y x-y x−y
- 选择一个数 z ( 2 ≤ z ≤ x ) z(2 \le z \le x) z(2≤z≤x),将 x x x变为 x / z x/z x/z
问有多少种方案数可以将整数 n n n变为 1 1 1,输出方案数对 m m m取模
思路
考虑 d p dp dp, d p [ i ] dp[i] dp[i]表示整数 i i i变为 1 1 1的方案数
容易得出转移方程为 d p [ i ] = ∑ j = 1 i − 1 d p [ j ] + ∑ j = 2 i d p [ i / j ] dp[i] = \sum_{j=1}^{i-1}dp[j] + \sum_{j=2}^{i}dp[i/j] dp[i]=∑j=1i−1dp[j]+∑j=2idp[i/j]
如果直接求解时间复杂度为 O ( n 2 ) O(n^2) O(n2),则需要优化
容易看出 ∑ j = 1 i − 1 d p [ j ] \sum_{j=1}^{i-1}dp[j] ∑j=1i−1dp[j]是一个前缀和,用一个变量 s u m sum sum存下即可
∑ j = 2 i d p [ i / j ] \sum_{j=2}^{i}dp[i/j] ∑j=2idp[i/j] 中的 i / j ( 2 ≤ j ≤ i ) i/j(2 \le j \le i) i/j(2≤j≤i) 容易看出是整数分块
tips
下面是整数分块的解释
对于一个整数 n n n,容易知道存在一个区间 ∀ i ∈ [ l , r ] \forall i \in [l,r] ∀i∈[l,r] 有 n / i n/i n/i相等
整数分块就是告诉你如果已知 l l l,那么 r = n / ( n / l ) r = n / (n/l) r=n/(n/l),至于为什么?这里以后有时间了再补吧
for(int l = 1,r ; l <= n ; l = r + 1){
r = n / (n / l);//区间[l,r]内 n/i的值都为n/l
}
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
inline ll read() {
ll s = 0, w = 1;
char ch = getchar();
while (ch < 48 || ch > 57) {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= 48 && ch <= 57)
s = (s << 1) + (s << 3) + (ch ^ 48), ch = getchar();
return s * w;
}
ll f[200005];
int main() {
f[1] = 1;
ll n = read(), mod = read();
ll sum = 0;//sum为前缀和
for (int i = 2; i <= n; ++i) {
sum = (sum + f[i - 1]) % mod;
f[i] = sum;
for (int l = 2, r; l <= i; l = r + 1) {//因为n/i时i是从2开始
r = i / (i / l);
f[i] = (f[i] + (r - l + 1) * f[i / l]) % mod;
}
}
cout << f[n] << endl;
}