[SDOI2015]约数个数和
[SDOI2015]约数个数和
闲话:莫比乌斯反演经典题
题目大意
求
组数据,且
良心样例
输入样例
2
7 4
5 6
输出样例
110
121
一条有用的结论
小小的证明
对于一个 ,我们构造
现在我们就要找到 之间的关系,使我们能通过 得到 ,而且能与所有的 一一对应
显然得到
由
得
又因为
所以
把所得的结论综合起来
此时对于满足 的 都可以得到
并且与所有的 以一对应
也就是说用满足上述条件的 来构造 ,能与所有的 一一对应
故这条有用的结论得证
正式切题
然后开始推式子
此时可以从两个方面入手,利用
或
化简式子
但无论如何,化简后的式子是一样的
所谓“殊途同归”
第一种推法
似乎已经有人注意到了
忽然出现了个
其实不必惊讶
我们发现后面那堆是因为这两个式子只和 有关
故我们可以令
然后套用
发现一个问题
我们如何处理
你可以选择用到的时候再算
也可以 预处理
如果选择前者就要注意了
计算过了的 就要记录下来,否则会超时
事实上
我的两种算 的方式在洛谷评测机前者跑得更快
尤其是在总时间上,快了差不多
第二种推法
如旧有
设
再设
考虑反演
而我们需要的答案是
试试看
其实早在第二行的时候就“殊途同归”了!!
代码
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); } }
实际上
关于处理 的办法有很多
再说一种
注意到
噢噢,其实 就是 的前缀和
于是乎,线筛上
具体来说,约数个数定理不用白不用
线性筛时记录最小质因子的次数即可(因为只会被最小质因子筛到)。
此时预处理成了
似乎还有杜教筛~~~
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· 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工具