前置知识
狄利克雷卷积
在学习 杜教筛
的时候,想必已经接触到了这个东西。
( f ∗ g ) ( n ) = ∑ d ∣ n f ( d ) g ( n d ) (f*g)(n)=\sum_{d|n}f(d)g(\frac{n}{d}) (f∗g)(n)=d∣n∑f(d)g(dn)
其中 ( f ∗ g ) (f*g) (f∗g) 表示 f f f 和 g g g 的狄利克雷卷积。
说白了,普通卷积是下标相加,狄利克雷是下标相乘。
它有几个著名的性质:
- 计算一次的时间复杂度是 O ( n ln n ) \mathcal O(n\ln n) O(nlnn) 。其中 ln n \ln n lnn 来自于著名的调和级数 ∑ x = 1 n x − 1 \sum_{x=1}^{n}x^{-1} ∑x=1nx−1 。
- 满足交换律、结合律——就像普通卷积一样。毕竟乘法是拥有交换律、结合律的。
常函数
就是 I ( x ) = 1 I(x)=1 I(x)=1 ,没什么好说的。
题目
题目描述
给定
f
(
1
)
,
f
(
2
)
,
f
(
3
)
,
…
,
f
(
n
)
f(1),f(2),f(3),\dots,f(n)
f(1),f(2),f(3),…,f(n) 和
k
k
k ,对于
∀
x
∈
[
1
,
n
]
\forall x\in[1,n]
∀x∈[1,n] ,求
g ( x ) = ∑ x 1 ∣ x ∑ x 2 ∣ x 1 ∑ x 3 ∣ x 2 ⋯ ∑ x k ∣ x k − 1 f ( x k ) g(x)=\sum_{x_1|x}\sum_{x_2|x_1}\sum_{x_3|x_2}\dots\sum_{x_k|x_{k-1}}f(x_k) g(x)=x1∣x∑x2∣x1∑x3∣x2∑⋯xk∣xk−1∑f(xk)
输出答案除以 ( 1 0 9 + 7 ) (10^9+7) (109+7) 的余数。
数据范围与约定
对于
30
%
30\%
30% 的数据,
n
≤
100
,
k
≤
5
n ≤ 100, k ≤ 5
n≤100,k≤5 。
对于 100 % 100\% 100% 的数据, n ≤ 100000 , k ≤ 100000 , T ≤ 5 n ≤ 100000, k ≤ 100000, T ≤ 5 n≤100000,k≤100000,T≤5 。
思路
暴力
对于 30 % 30\% 30% 的数据,约数的数量并不多,层数也不多, d f s \tt{dfs} dfs 即可。
动态规划
发现有重复计算的情况。于是考虑记忆化搜索。也就是动态规划。
令 g i ( x ) = ∑ x 1 ∣ x ∑ x 2 ∣ x 1 ∑ x 3 ∣ x 2 ⋯ ∑ x i ∣ x i − 1 f ( x i ) g_{i}(x)=\sum_{x_1|x}\sum_{x_2|x_1}\sum_{x_3|x_2}\dots\sum_{x_i|x_{i-1}}f(x_i) gi(x)=x1∣x∑x2∣x1∑x3∣x2∑⋯xi∣xi−1∑f(xi)
那么一定有 g i ( x ) = ∑ d ∣ x g i − 1 ( d ) g_{i}(x)=\sum_{d|x}g_{i-1}(d) gi(x)=d∣x∑gi−1(d)
答案就是
g
k
(
x
)
g_{k}(x)
gk(x) ,所以递推即可。可惜跟暴力分差不了多少。
卷积优化
然而这玩意儿……长得跟 狄利克雷卷积 很像!
我们把 I I I 用进去,发现——
g i ( x ) = ∑ d ∣ x g i − 1 ( d ) ⋅ I ( x d ) = ( g i − 1 ∗ I ) ( x ) g_{i}(x)=\sum_{d|x}g_{i-1}(d)\cdot I\left(\frac{x}{d}\right)=(g_{i-1}*I)(x) gi(x)=d∣x∑gi−1(d)⋅I(dx)=(gi−1∗I)(x)
没错!这就是狄利克雷卷积的形式!
结合边界条件 g 0 ( x ) = f ( x ) g_{0}(x)=f(x) g0(x)=f(x)
得到这样一个式子 g i = { [ ( f ∗ I ) ∗ I ] ∗ I } ∗ ⋯ ∗ I g_{i}=\{[(f*I)*I]*I\}*\cdots*I gi={[(f∗I)∗I]∗I}∗⋯∗I
但是,狄利克雷卷积是拥有 结合律 的!
所以,我们把它简写一下: g k = f ∗ I k g_k=f*I^k gk=f∗Ik
拥有结合律的幂,是可以使用 快速幂 的。所以复杂度就只有 O ( n log n log k ) \mathcal O(n\log n\log k) O(nlognlogk) 了。
代码
如果你不知道狄利克雷卷积应该怎么写(自然就搞不清楚复杂度),我贴一份代码,仅供参考。
void multiply(int a[],int b[],int c[]){
/* a*b的值存在c里面 */
memset(c,0,sizeof c);
for(int i=1; i<=n; ++i)
for(int j=1; j<=n/i; ++j)
c[i*j] += a[i]*b[j];
}
于是其复杂度就是 n + n 2 + n 3 + ⋯ + n n = O ( n ln n ) n+\frac{n}{2}+\frac{n}{3}+\cdots+\frac{n}{n}=\mathcal O(n\ln n) n+2n+3n+⋯+nn=O(nlnn) 。