SDOI 2015 约数个数和
SDOI 2015 约数个数和
链接: https://www.luogu.com.cn/problem/P3327
设 \([x]\) 为逻辑判断函数,若 \(x\) 为真,则值为 \(1\),否则值为 \(0\)。
一个重要的结论:
\[d(i\times j)=\sum_{x|i}\sum_{y|j}[gcd(x,y)=1]
\]
那么原式化为:
\[\sum_{i=1}^n\sum_{j=1}^m\sum_{x|i}\sum_{y|j}[gcd(x,y)=1]
\]
根据莫比乌斯反演有:
\[\sum_{i=1}^n\sum_{j=1}^m\sum_{x|i}\sum_{y|j}\sum_{d|gcd(x,y)}\mu(d)
\]
考虑将枚举因子改成枚举倍数:
\[\begin{aligned}
&=\sum_{d}^{\min(n,m)}\mu(d)\sum_{i=1}^{\lfloor\frac{n}
{d}\rfloor}\sum_{j=1}^{\lfloor\frac{m}{d}\rfloor}\sum_{x|i}\sum_{y|i}1
\\
&=\sum_{d}^{\min(n,m)}\mu(d)\sum_{x=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{y=1}^{\lfloor\frac{m}{d}\rfloor}\lfloor\dfrac{\lfloor\frac{n}{d}\rfloor}{x}\rfloor\lfloor\dfrac{\lfloor\frac{m}{d}\rfloor}{y}\rfloor
\\
&=\sum_{d}^{\min(n,m)}\mu(d)\times\sum_{x=1}^{\lfloor\frac{n}{d}\rfloor}\lfloor\dfrac{\lfloor\frac{n}{d}\rfloor}{x}\rfloor\times\sum_{y=1}^{\lfloor\frac{m}{d}\rfloor}\lfloor\dfrac{\lfloor\frac{m}{d}\rfloor}{y}\rfloor
\end{aligned}
\]
相当于是枚举 \(x,y\) 然后乘上它在枚举范围内的倍数个数,即它作出贡献的次数。而这个式子有 \(\lfloor\dfrac{n}{d}\rfloor,\lfloor\dfrac{m}{d}\rfloor\)。那么我们可以考虑用数论分块优化。
设 \(f(x)\) 为:
\[\sum_{i=1}^{x}\lfloor\dfrac{x}{i}\rfloor
\]
这个式子可以通过枚举 \(i\) 和 \(x\) 在 \(O(n^2)\) 的时间内预处理,而可以用数论分块优化到 \(O(n\sqrt n)\)。
那么答案就是:
\[\sum_d^{\min(n,m)}\mu(d)\times f(\lfloor\frac{n}{d}\rfloor)\times f(\lfloor\frac{m}{d}\rfloor)
\]
这个式子在预处理后可以在 \(O(n)\) 的时间内求得,而利用数论分块则可以优化到 \(O(\sqrt n)\)。
那么总时间复杂度就是 \(O(T\sqrt n+n\sqrt n)\)。
代码如下:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN = 5e4+5;
const int MX = 5e4;
int n,m,prime[MAXN],pc,p[MAXN];
ll mu[MAXN],g[MAXN];
int main()
{
mu[1]=1;
for(int i=2;i<=MX;++i)
{
if(!p[i]) prime[++pc]=i,mu[i]=-1;
for(int j=1;j<=pc&&1ll*i*prime[j]<=MX;++j)
{
p[i*prime[j]]=1;
if(i%prime[j]==0)
{
mu[i*prime[j]]=0;
break;
}
mu[i*prime[j]]=-mu[i];
}
}
for(int i=1;i<=MX;++i) mu[i]+=mu[i-1];
for(int i=1;i<=MX;++i)
{
ll ans=0;
for(int l=1;l<=i;l=(i/(i/l))+1)
{
int r=i/(i/l);
r=min(r,i);
ans+=1ll*(r-l+1)*(i/l);
}
g[i]=ans;
}
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d %d",&n,&m);
int now=1,l1=1,l2=1,up=min(n,m);
ll ans=0;
while(now<=up)
{
int r=min(n/(n/l1),m/(m/l2));
r=min(r,up);
ans+=1ll*(mu[r]-mu[now-1])*g[n/l1]*g[m/l2];
now=r+1;
if(n/(n/l1)<=m/(m/l2)) l1=n/(n/l1)+1;
else l2=m/(m/l2)+1;
}
printf("%lld\n",ans);
}
return 0;
}
路漫漫其修远兮,吾将上下而求索。