【题解】 [NOI Online #1 入门组]跑步 dp+根号分治+拆分数 Luogu6189
Legend
Link \(\textrm{to Luogu}\)。
求 \(n\) 的拆分数。
\(1 \le n \le 10^5\)。
Editorial
Simple \(O(n \sqrt{n})\)
考虑到一个事实:\(\le \sqrt{n}\) 的数字只有 \(O(\sqrt{n})\) 个,可以暴力完全背包。
完全背包显然没有人不会。复杂度 \(O(n \sqrt{n})\)
考虑到另一个事实:\(>\sqrt{n}\) 的数字最多只会被选 \(O(\sqrt{n})\) 个,可以暴力 \(dp\)。
怎么 \(dp\)?设 \(dp_{i,j}\) 为把 \(j\) 拆分成 \(i\) 个数字的方案数,总状态数是 \(O(n \sqrt{n})\) 的。
转移有 \(dp_{i,j}=dp_{i-1,j-(\sqrt{n}+1)}+dp_{i,j-i}\) 两种。
含义分别为选择一个 \(\sqrt{n}+1\) 大小的物体和把所有物体高度增加 \(1\)。这样可以涵盖所有情况。
这一部分复杂度也是 \(O(n \sqrt{n})\)。
再用 \(O(n)\) 合并两个背包的答案,总复杂度 \(O(n \sqrt{n})\)。
$O(n \log n) $
一切的前提,模数要是 \(\rm{NTT}\) 模数。显然本题用不了这个做法。
考虑毒瘤的生成函数做法:
拆分数的生成函数显然为 \(F(x)=\prod\limits_{i=1}^{\infty} \sum\limits_{j=0}^{\infty}(x^i)^j\)。
根据 \(\sum_{i=0}^{\infty} x^i=\frac{1}{1-x}\) 可以得出 \(F(x)=\prod\limits_{i=1}^{\infty} \dfrac{1}{1-x^i}\)。
两边同时取 \(\ln\) 有 \(\ln F(x)= -\sum\limits_{i=1}^{\infty}\ln (1-x^i)\)。
现在的问题就是如何计算 \(h(x)=\ln(1-x)\),因为 \(\ln F(x)= -\sum\limits_{i=1}^{\infty}h(x^i)\)。
求个导数 \(h'(x)=-\frac{1}{1-x}=-\sum\limits_{i=0}^{\infty}x^i\)。
同时积分有 \(h(x)= - \sum\limits_{i=1}^{\infty}\frac{x^i}{i}\),求完了。
带回 \(F(x)\) 有 \(\ln F(x)= \sum\limits_{i=1}^{\infty}\sum\limits_{j=1}^{\infty}\frac{x^{ij}}{j}=\sum\limits_{t=1}^{\infty}x^t \sum\limits_{j|t}\frac{1}{j}\)。
显然这个式子可以调和级数 \(O(n \log n)\) 预处理完。
再把式子 \(\exp\) 回去就做完了。复杂度就是 \(O(n \log n)\)。
Code
采用的根号分治做法。
#include <bits/stdc++.h>
#define debug(...) fprintf(stderr ,__VA_ARGS__)
using namespace std;
int n ,p;
const int MX = 1e5 + 23;
const int SIZE = 320;
int dp[MX];
int f[SIZE][MX] ,dp2[MX];
int main(){
cin >> n >> p;
dp[0] = 1 % p;
for(int i = 1 ; i <= min(n ,SIZE) ; ++i){
for(int j = i ; j <= n ; ++j){
dp[j] = (dp[j] + dp[j - i]) % p;
}
}
dp2[0] = f[0][0] = 1 % p;
for(int i = 1 ; i < SIZE ; ++i){
for(int j = SIZE + 1 ; j <= n ; ++j){
f[i][j] = (f[i - 1][j - (SIZE + 1)] + f[i][j - i]) % p;
dp2[j] = (dp2[j] + f[i][j]) % p;
}
}
int Ans = 0;
for(int i = 0 ; i <= n ; ++i){
Ans = (Ans + 1LL * dp[i] * dp2[n - i]) % p;
}
printf("%d\n" ,Ans);
return 0;
}