[SDOI2015]约数个数和

[SDOI2015]约数个数和

闲话:莫比乌斯反演经典题

题目大意

i=1nj=1mσ0(ij)

T 组数据,且 1T,n,m50000
良心样例

输入样例

2
7 4
5 6

输出样例

110
121

一条有用的结论

σ0(ij)=x|iy|j[gcd(x,y)=1]

小小的证明
对于一个 d|ij ,我们构造 x=igcd(i,d),y=dgcd(i,d)
现在我们就要找到 x,y 之间的关系,使我们能通过 x,y 得到 d,而且能与所有的 d 一一对应
显然得到 gcd(x,y)=1,x|i
ygcd(i,j)|ij
y|xj
又因为 gcd(x,y)=1
所以 y|j
把所得的结论综合起来
gcd(x,y)=1,x|i,y|j
此时对于满足 gcd(x,y)=1,x|i,y|jx,y 都可以得到 iyx|ij
并且与所有的 d|ij 以一对应
也就是说用满足上述条件的 x,y 来构造 d ,能与所有的 d 一一对应
故这条有用的结论得证

正式切题

然后开始推式子
此时可以从两个方面入手,利用

d|nμ(d)=[n=1]

F(x)=x|df(d)f(x)=x|dμ(dx)F(d)

化简式子
但无论如何,化简后的式子是一样的
所谓“殊途同归”

第一种推法

i=1nj=1mσ0(ij)=i=1nj=1mx|iy|j[gcd(x,y)=1]=i=1nj=1mx|iy|jd|gcd(x,y)μ(d)=d=1min(n,m)μ(d)d|xd|yx|iy|j1=d=1min(n,m)μ(d)d|xd|ynxmy=d=1min(n,m)μ(d)x=1ndy=1mdndxmdy=d=1min(n,m)μ(d)f(nd)f(md)

似乎已经有人注意到了
忽然出现了个 f
其实不必惊讶
我们发现后面那堆是因为这两个式子只和 nd,md 有关
故我们可以令 f(n)=i=1nni
然后套用
发现一个问题
我们如何处理 f
你可以选择用到的时候再算 f
也可以 O(nn) 预处理
如果选择前者就要注意了
计算过了的 f 就要记录下来,否则会超时
事实上
我的两种算 f 的方式在洛谷评测机前者跑得更快
尤其是在总时间上,快了差不多 2s

第二种推法
如旧有

i=1nj=1mσ0(ij)=i=1nj=1mx|iy|j[gcd(x,y)=1]

f(d)=i=1nj=1mx|iy|j[gcd(x,y)=d]
再设 F(d)=d|kf(k)=i=1nj=1mx|iy|j[d|gcd(x,y)]
考虑反演
f(d)=d|kμ(kd)F(k)
而我们需要的答案是 f(1)
试试看

f(1)=d=1min(n,m)F(d)μ(d)=d=1min(n,m)μ(d)i=1nj=1mx|iy|j[d|gcd(x,y)]=d=1min(n,m)μ(d)d|xd|yx|iy|j1=d=1min(n,m)μ(d)d|xd|ynxmy=d=1min(n,m)μ(d)x=1ndy=1mdndxmdy=d=1min(n,m)μ(d)f(nd)f(md)

其实早在第二行的时候就“殊途同归”了!!

代码

f的第一中处理方式

#include<cstdio>
#include<iostream>
using namespace std;
typedef long long LL;
const int N = 50000;
int n , m , T , tot , vis[N + 5] , mu[N + 5] , prime[N + 5];
LL ans , f[N + 5];
inline void getm()
{
mu[1] = 1;
for(register int i = 2; i <= N; i++)
{
if (!vis[i]) prime[++tot] = i , mu[i] = -1;
for(register int j = 1; prime[j] * i <= N && j <= tot; j++)
{
vis[prime[j] * i] = 1;
if (i % prime[j] == 0) break;
mu[prime[j] * i] = -mu[i];
}
}
for(register int i = 2; i <= N; i++) mu[i] += mu[i - 1];
}
inline LL getf(int n)
{
int r;
LL res = 0;
for(register int l = 1; l <= n; l = r + 1)
{
r = n / (n / l);
res += 1LL * (r - l + 1) * (n / l);
}
return res;
}
int main()
{
getm();
for(register int i = 1; i <= N; i++) f[i] = -1;
scanf("%d" , &T);
while (T--)
{
scanf("%d%d" , &n , &m);
int r;
ans = 0;
for(register int l = 1; l <= min(n , m); l = r + 1)
{
r = min(n / (n / l) , m / (m / l));
if (f[n / l] == -1) f[n / l] = getf(n / l);
if (f[m / l] == -1) f[m / l] = getf(m / l);
ans += f[n / l] * f[m / l] * (mu[r] - mu[l - 1]);
}
printf("%lld\n" , ans);
}
}

f的第二种处理方式

#include<cstdio>
#include<iostream>
using namespace std;
typedef long long LL;
const int N = 50000;
int n , m , T , tot , vis[N + 5] , mu[N + 5] , prime[N + 5];
LL ans , f[N + 5];
inline void getm()
{
mu[1] = 1;
for(register int i = 2; i <= N; i++)
{
if (!vis[i]) prime[++tot] = i , mu[i] = -1;
for(register int j = 1; prime[j] * i <= N && j <= tot; j++)
{
vis[prime[j] * i] = 1;
if (i % prime[j] == 0) break;
mu[prime[j] * i] = -mu[i];
}
}
for(register int i = 2; i <= N; i++) mu[i] += mu[i - 1];
}
inline void getf()
{
int r;
for(register int i = 1; i <= N; i++)
for(register int l = 1; l <= i; l = r + 1)
{
r = i / (i / l);
f[i] += (r - l + 1) * (i / l);
}
}
int main()
{
getm() , getf();
scanf("%d" , &T);
while (T--)
{
scanf("%d%d" , &n , &m);
int r;
ans = 0;
for(register int l = 1; l <= min(n , m); l = r + 1)
{
r = min(n / (n / l) , m / (m / l));
ans += f[n / l] * f[m / l] * (mu[r] - mu[l - 1]);
}
printf("%lld\n" , ans);
}
}

实际上
关于处理 f 的办法有很多
再说一种
注意到

f(n)=i=1nni=i=1ni|d1=d=1ni|d1=d=1nσ0(d)

噢噢,其实 f 就是 σ0 的前缀和
于是乎,线筛上
具体来说,约数个数定理不用白不用
线性筛时记录最小质因子的次数即可(因为只会被最小质因子筛到)。
此时预处理成了 O(n)

似乎还有杜教筛~~~

posted @   leiyuanze  阅读(283)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示