LCM 求和问题的新优选!
tzhengqing
·
·
算法·理论
好像也并没有很优
前言
本文将给出积性函数 h 满足 \sum\limits_{d|n}h(d)=\frac{1}{n} 和拓展 \sum\limits_{d|n}h_k(d)=\frac{1}{n^k},
将 \gcd 代入 n 即可得到 LCM 求和问题较为简单的做法。
注意 h 函数的用处一般在于简化推导而不是降低复杂度。
本文有时会使用 n/d 来表示 \lfloor\frac{n}{d}\rfloor,这样比较方便且简明。
引入
我们在爆杀莫反题的时候有可能会遇到诸如 \frac{1}{\gcd(i,j)} 的式子,
一般情况下我们会使用钦定法,再把单位函数拆为莫反,交换求和解决问题。
例如求 \sum\limits_{i=1}^{n}\operatorname{lcm}(i,n):
:::error[石山步骤]
\begin{align*}
\sum\limits_{i=1}^{n}\operatorname{lcm}(i,n)&=n\sum\limits_{i=1}^{n}\frac{i}{\gcd(i,n)}\\
&=n\sum\limits_{d|n}\frac{1}{d}\sum\limits_{i=1}^{n}i[\gcd(i,n)=d]\\
&=n\sum\limits_{d|n}\sum\limits_{i=1}^{n/d}i[\gcd(i,n/d)=1]\\
&=n\sum\limits_{d|n}\sum\limits_{i=1}^{n/d}i\sum\limits_{k | \gcd(i,n/d)} \mu(k)\\
&=n\sum\limits_{d|n}\sum\limits_{k | \frac{n}{d}} k\mu(k)\sum\limits_{i=1}^{n/dk}i\\
&=n\sum\limits_{d|n}\sum\limits_{k | \frac{n}{d}} k\mu(k)\frac{(n/dk)(n/dk+1)}{2}
\end{align*}
:::
虽然这样做出来了,但是推起来太麻烦了!这还只是最基础的 LCM 求和而已。
发现问题出在 \gcd 在分母不能直接加减,我们想到,如果构造函数(夹带私货地,不妨称为 h),使得
\sum\limits_{d|n}h(d)=\frac{1}{n}
那我们就可以避免除法直接交换顺序求和了!
研究
积性
由定义式,反演得到
h(n)=\frac{1}{n}\sum\limits_{d|n}d\mu(d).
那这个一眼积性函数了,性质多多的。
注意其取值范围为 \R,需要模数为质数且大于 n 才能用。
质数幂
考察 \sum\limits_{d|n}d\mu(d) 在 1,p,p^2,...,p^x 的取值分别为 1,-p,0,...,0,可以得到
h(p^x)=\frac{1-p}{p^x}.
欧拉筛
上式可以简单做欧拉筛,对于 x \geq 2 有
h(p^x)=\frac{h(p^{x-1})}{p}.
写成代码就是 h[d]=h[i]*qpow(pr[j],p-2)%p,也是非常的简单。
这里贴出一份 O(n \log n) 的完整代码。
:::success[Code]
#define int long long
const int N=1145141,p=998244353;//可修改的参数
int h[N],cnt=0,pr[N],isp[N];
inline int qpow(int a,int b)
{
int x=1,y=a;
while(b)
{
if(b&1)x=x*y%p;
y=y*y%p;
b>>=1;
}
return x;
}
void init()
{
h[1]=1;
for(int i=2; i<N; i++)
{
if(!isp[i])
{
pr[++cnt]=i;
h[i]=qpow(i,p-2)-1;
}
for(int j=1; j<=cnt&&i*pr[j]<N; j++)
{
int d=i*pr[j];
isp[d]=1;
if(i%pr[j]==0)
{
h[d]=h[i]*qpow(pr[j],p-2)%p;
break;
}
h[d]=h[i]*h[pr[j]]%p;
}
}
}
预处理逆元可以做到 O(n)。不想写 |
| ### 求和问题 |
| 很遗憾求前缀和是比较困难的,反演之后出现的调和级数堵死了大部分路径。 |
希望有兴趣的大神们可以研究。当然我认为这是不可做的。 |
| ### 拓展 |
| 有的时候还需要 $\sum\limits_{d |
| ## 在 LCM 求和问题的应用 |
| ### 基础 |
| 例题 1:多组询问求 \sum\limits_{i=1}^{n}\operatorname{lcm}(i,n)。 |
:::info[Solution]
直接上 h 函数,看看优化效果:
\begin{align*}
\sum\limits_{i=1}^{n}\operatorname{lcm}(i,n)&=n\sum\limits_{i=1}^{n}i\sum\limits_{d|i,d|n}h(d)\\
&=n\sum\limits_{d|n}dh(d)\sum\limits_{i=1}^{n/d}i\\
&=n\sum\limits_{d|n}dh(d)(\frac{(n/d)(n/d+1)}{2})
\end{align*}
轻松秒杀。记忆化一下,均摊时间复杂度 O(\log n)。
不是怎么没有模数??直接只剩理论价值了。模数的性质要比较优秀才能使用 h 函数。
主包尝试在这里取 p=100000000010299 再使用 __int128,卡了三发评测,对不起。/ll
:::
例题 2:求 \sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}\operatorname{lcm}(i,j)。
:::info[Solution]
这题也是拉完了,建议降绿好吧。直接上 h 函数:(设 n<m)
\begin{align*}
\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}\operatorname{lcm}(i,j)&=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}ij\sum\limits_{d|i,d|j}h(d)\\
&=\sum\limits_{d=1}^{n}d^2h(d)\sum\limits_{i=1}^{n/d}\sum\limits_{j=1}^{m/d}ij\\
&=\sum\limits_{d=1}^{n}d^2h(d)(\frac{(n/d)(n/d+1)}{2})(\frac{(m/d)(m/d+1)}{2})
\end{align*}
这不秒了吗。虽然本来也不难。
这个做法的好处是形式上也相当简单,不用做整除分块了。
如果带整除分块还可以开多测,时间复杂度 O(n+T\sqrt{n})。
随手贴一个代码吧,虽然实现有点劣。跑了大概 600ms。
:::success[Code]
#include<bits/stdc++.h>
using namespace std;
#define int unsigned long long
const int N=10000005,p=20101009;
int h[N],cnt=0,pr[N],isp[N];
inline int qpow(int a,int b)
{
int x=1,y=a;
while(b)
{
if(b&1)x=x*y%p;
y=y*y%p;
b>>=1;
}
return x;
}
void init()
{
h[1]=1;
for(int i=2; i<N; i++)
{
if(!isp[i])
{
pr[++cnt]=i;
h[i]=qpow(i,p-2)-1;
}
for(int j=1; j<=cnt&&i*pr[j]<N; j++)
{
int d=i*pr[j];
isp[d]=1;
if(i%pr[j]==0)
{
h[d]=h[i]*qpow(pr[j],p-2)%p;
break;
}
h[d]=h[i]*h[pr[j]]%p;
}
}
}
int S(int x){return x*(x+1)/2%p;}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int n,m,ans=0;cin>>n>>m;
init();if(n>m)swap(n,m);
for(int i=1;i<=n;i++)ans=(ans+i*i%p*h[i]%p*S(n/i)%p*S(m/i))%p;
cout<<ans;
return 0;
}
:::
进阶
例题 3:求 \sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}\sum\limits_{k=0}^{m}\operatorname{lcm}^k(i,j)。
:::info[Solution]
利用 h_k,枚举 k,对于单个的 k,可以得到
\begin{align*}
\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}\operatorname{lcm}^k(i,j)&=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}i^kj^k\sum\limits_{d|i,d|j}h_k(d)\\
&=\sum\limits_{d=1}^{n}d^{2k}h_k(d)\sum\limits_{i=1}^{n/d}\sum\limits_{j=1}^{n/d}i^kj^k\\
&=\sum\limits_{d=1}^{n}d^{2k}h_k(d)P_k(n/d)^2.
\end{align*}
其中 P_k(x)=\sum\limits_{i=1}^{x}i^k,这个筛好 i^k 之后直接算就行。
对于所有的 k,总时间复杂度 O(nm)。
:::
提高
例题 4:求 \sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}\sum\limits_{a=0}^{m}\sum\limits_{b=0}^{k}\frac{\operatorname{lcm}^a(i,j)}{\gcd^b(i,j)}。
:::info[Solution]
例题 3 的略微加强版。
\begin{align*}
\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}\sum\limits_{a=0}^{m}\sum\limits_{b=0}^{k}\frac{\operatorname{lcm}^a(i,j)}{\gcd^{b}(i,j)}&=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}\sum\limits_{a=0}^{m}\sum\limits_{b=0}^{k}i^aj^a\sum\limits_{d|i,d|j}h_{a+b}(d)\\
&=\sum\limits_{d=1}^{n}\sum\limits_{a=0}^{m}\sum\limits_{b=0}^{k}h_{a+b}(d)d^{2a}\sum\limits_{i=1}^{n/d}\sum\limits_{j=1}^{n/d}i^aj^a\\
&=\sum\limits_{d=1}^{n}\sum\limits_{a=0}^{m}d^{2a}P_a(n/d)^2\sum\limits_{b=0}^{k}h_{a+b}(d)\\
&=\sum\limits_{d=1}^{n}\sum\limits_{a=0}^{m}d^{2a}P_a(n/d)^2Q_a(d)
\end{align*}
| 预处理 P_a 和 Q_a(d)=\sum\limits_{b=0}^{k}h_{a+b}(d),前缀和算就行,时间复杂度 O(n(m+k))。 |
例题 5:多组询问求 \sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}\sum\limits_{k=1}^{n}\operatorname{lcm}(i,j,k)\times i^i \times j^j \times k^k。
:::info[Solution]
记答案为 Ans(n)。
观察到这个东西可以做增量。
具体地,假如你已经知道了 Ans(n-1) 的值,你就可以钦定 i,j,k 中的几个是 n。
对着这个东西做容斥是方便的。
Ans(n)=Ans(n-1)+3n^n\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}\operatorname{lcm}(n,i,j)i^ij^j-3n^{2n}\sum\limits_{i=1}^{n}\operatorname{lcm}(n,i)i^i+n^{3n+1}.
复杂度瓶颈显然是求 g(n)=\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}\operatorname{lcm}(n,i,j)i^ij^j。
三元 \operatorname{lcm} 太讨厌了,必须拆开,注意到:
\operatorname{lcm}(n,i,j)=\frac{nij\gcd(n,i,j)}{\gcd(n,i)\gcd(n,j)\gcd(i,j)}
证明显然,分别考虑每个质因子即可。
考虑令 f_n(x)=x^x\frac{x}{\gcd(n,x)},则
g(n)=n\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}f_n(i)f_n(j)\frac{\gcd(n,i,j)}{\gcd(i,j)}
于是可以得到
g(n)=n\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}f_n(i)f_n(j)\sum\limits_{d|n,d|i,d|j}\varphi(d)\sum\limits_{e|i,e|j}h(e)
交换求和顺序,记 w=\operatorname{lcm}(d,e):
g(n)=n\sum\limits_{d|n}\varphi(d)\sum\limits_{e=1}^{n}h(e)\sum\limits_{i=1}^{n/w}\sum\limits_{j=1}^{n/w}f_n(iw)f_n(jw)
这里考虑令 F_n(x)=\sum\limits_{i=1}^{n/x}f_n(ix),显然这个东西可以 O(n \log n) 预处理,代入得到
g(n)=n\sum\limits_{d|n}\varphi(d)\sum\limits_{e=1}^{n}h(e)F_n(\operatorname{lcm}(d,e))^2.
于是直接计算,总时间复杂度为 O(n^2 \log n)。
对每个 d 预处理所有 \operatorname{lcm}(d,e) 再排序遍历可以做到 O(n^2),实测速度几乎没区别。
:::
欢迎大家探索更多的妙妙用法。真的会有吗
总结
当然 $h$ 函数在 LCM 求和问题也绝非万能,其局限性在于无法处理模数过小或无模数的问题。
$h$ 函数在有些例题能比正常莫反少一个 log,希望能有大神研究一下原因。
主包今年就要退役了,希望算法竞赛能够记得我,所以有了这篇文章。~~因此本文有一些私货。~~
:::info[AI 使用声明]
使用了 AI 查错。
:::