CF1097D 题解
NaCly_Fish
2019-01-05 20:12:25
[题目链接](https://www.luogu.com.cn/problem/CF1097D)
本题解已全面更新。
****
首先有一个重要观察:答案关于 $n$ 是积性的。原因很简单,设 $a_k(n)$ 为答案,就有一个显然的递归关系
$$a_k(n)=\frac{1}{\sigma_0(n)}\sum_{d|n}a_{k-1}(d)$$
其中 $\sigma_0(n)$ 表示 $n$ 的约数个数。根据积性函数的基本性质,两个积性函数的 Dirichlet 卷积(这里就是 Dirichlet 前缀和)仍然是积性的;两个积性函数对应点值相乘,也是积性的。
所以只要 $a_{k-1}(n)$ 是积性的,$a_k(n)$ 就是积性的。显然 $a_0(n)=n$ 是积性函数,故答案是积性的。
利用这个性质,就只要对 $n$ 做质因数分解,对每个质数幂求出答案然后相乘即可。也就是说,我们只用考虑算 $a_k(p^m)$,其中 $p$ 为质数。
此时直接用原本的递归关系来计算,复杂度就大大降低了,足以通过本题。
****
但是这样的 DP 还是太朴素,不符合我鱼鱼之风格,一定要想办法优化一下。
下文中,我们都考虑**只对一个固定的** $p$ 来求答案。所以为了方便设 $f_{i,j}=a_i(p^j)$,在 $f$ 中不写出 $p$。
在质数幂上,原本的递归关系就变为(有边界值 $f_{0,j}=p^j$):
$$f_{i,j}=\frac{1}{j+1}\sum_{k=0}^jf_{i-1,k}$$
这个递推式比较特别,第 $i$ 行的信息可以只从第 $i-1$ 行推过来,这是解迭代列可以做的问题。我们设
$$F_i(x)=\sum_{j=0}^\infty f_{i,j}x^j$$
递推式就可以写为(对等式两边分别提取 $x^j$ 项系数,对应的就是原式的等式两边)
$$(xF_i(x))'=\frac{F_{i-1}(x)}{1-x}$$
这样似乎不太好看,可以设 $G_i(x)=xF_i(x)$,就改写为
$$(1-x)xG_i'(x)=G_{i-1}(x)$$
但这样还是不便于处理,就要考虑**基变换换元**了。具体来说,我们希望找到合适的 $u$(也是一个关于 $x$ 的式子),满足 $H_i(u)=G_i(x)$,同时有
$$u \frac{\text d H_i(u)}{\text d u}=H_{i-1}(u)$$
根据链式法则:
$$ \frac{\text dG_i(x)}{\text dx}= \frac{\text dH_i(u)}{\text d u} \frac{\text du}{\text dx}$$
在等式两边同乘 $(1-x)x$,左边就是 $G_{i-1}(x)$,同时也是 $H_{i-1}(u)$。这样就得到
$$H_{i-1}(u)=u \frac{\text dH_i(u)}{\text du}=x(1-x)\frac{\text dH_i(u)}{\text d u} \frac{\text du}{\text dx}$$
化简一下,就得到关于 $u$ 的微分方程:
$$u=x(1-x) \frac{\text du}{\text dx}$$
解得 $u=x/(1-x)$。根据前面的边界条件可知 $F_0(x)=(1-px)^{-1}$,也就可以解出
$$H_0(u)=\frac{u}{1-(p-1)u}=\sum_{i\geq 0}(p-1)^iu^{i+1}$$
关于 $uH_i'(u)=H_{i-1}(u)$ 这个性质,将等式两边同时提取 $u^i$ 系数就会发现,每次迭代就是在 $u^i$ 系数上除以 $i$ 而已。这样就可以轻易地写出 $H_k(u)$:
$$H_k(u)=\sum_{i\geq 0}\frac{(p-1)^i}{(i+1)^k}u^{i+1}$$
接下来的工作就很简单了,再把 $H_k(u)$ 变回 $F_k(x)$ 即可:
$$\begin{aligned}f_{k,m}&=[x^m]\frac 1xH_k\left( \frac{x}{1-x}\right) \\ &= \sum_{i\geq 0} \frac{(p-1)^i}{(i+1)^k} [x^{m+1}] \left( \frac{x}{1-x}\right)^i \\ &= \sum_{i=0}^m \binom mi \frac{(p-1)^i}{(i+1)^k}\end{aligned}$$
直接计算即可,$(i+1)^{-k}$ 可以先用线性筛求幂,然后再线性求逆元,单次时间复杂度为 $\Theta(m+\pi(m) \log k)$,其中 $\pi(m)$ 表示不超过 $m$ 的质数个数。
***
最后我们来分析一下总时间复杂度,首先有一个分解质因数的复杂度,一般为 $\sqrt n$ 或 $\sqrt[4]{n}$。
然后是算 $f_{k,m}$ 的时间复杂度,由于 $m$ 之和有一个显然的上界 $\log_2 n$,且质因数多于一个时,$\pi(m)$ 之和总会减少。也就是说,这部分的时间复杂度不会超过 $\mathcal O(\log_2 n +\pi(\log_2 n)\log k)$。
这里用的是朴素分解质因数,总时间复杂可以写为
$$\mathcal O\left(\sqrt n+ \frac{ \log k}{\log \log n}\log n\right)$$
```cpp
#include<cstdio>
#define N 10003
#define ll long long
#define p 1000000007
using namespace std;
inline int power(int a,int t){
int res = 1;
while(t){
if(t&1) res = (ll)res*a%p;
a = (ll)a*a%p;
t >>= 1;
}
return res;
}
int fac[N],ifac[N];
void init(int n){
fac[0] = fac[1] = ifac[0] = ifac[1] = 1;
for(int i=2;i<=n;++i) fac[i] = (ll)fac[i-1]*i%p;
ifac[n] = power(fac[n],p-2);
for(int i=n-1;i;--i) ifac[i] = (ll)ifac[i+1]*(i+1)%p;
}
inline int binom(int n,int m){
if(n<m) return 0;
return (ll)fac[n]*ifac[m]%p*ifac[n-m]%p;
}
inline int solve(int n,int m,int q){
int res = 0,tmp = 1;
for(int i=0;i<=m;++i){
res = (res+(ll)binom(m,i)*tmp%p*power(i+1,p-n-1))%p;
tmp = (ll)tmp*(q-1)%p;
}
return res;
}
ll n;
int k,ans = 1;
int main(){
scanf("%lld%d",&n,&k);
init(233);
for(ll i=2;i*i<=n;++i){
if(n%i!=0) continue;
int cnt = 0;
while(n%i==0) n /= i,++cnt;
ans = (ll)ans*solve(k,cnt,i%p)%p;
}
if(n>1) ans = (ll)ans*solve(k,1,n%p)%p;
printf("%d",ans);
return 0;
}
```