【ybt金牌导航8-7-1】数对统计 / 关于莫比乌斯函数的少量内容

数对统计

题目链接:ybt金牌导航8-7-1

题目大意

给你 n,m,求 gcd(x,y)=1 的数对个数。
1<=x<=n,1<=y<=m

思路

莫比乌斯函数

什么东西

首先我们要知道莫比乌斯函数是个什么鬼东西。

首先,我们先不管莫比乌斯函数,先来看一个函数:F(n)=d|nf(d)
(假设 f(d) 是一个给出的函数)

那根据定义,我们可以先弄一下:
F(1)=f(1)
F(2)=f(1)+f(2)
F(3)=f(1)+f(3)
F(4)=f(1)+f(2)+f(4)
F(5)=f(1)+f(5)
F(6)=f(1)+f(2)+f(3)+f(6)
F(7)=f(1)+f(7)
F(8)=f(1)+f(2)+f(4)+f(8)

那我们考虑用 F(n) 来推 f(n)
f(1)=F(1)
f(2)=F(2)F(1)
f(3)=F(3)F(1)
f(4)=F(4)F(2)
f(5)=F(5)F(1)
f(6)=F(6)F(3)F(2)+F(1)
f(7)=F(7)F(1)
f(8)=F(8)F(4)

那我们会发现,它只会由它因数的 F 值加减或者不要组成。

那我们可以把它弄成这样的形式:
f(n)=d|nμ(d)F(nd)

那我们的莫比乌斯函数 μ(d) 就出现了!

定义

  1. 如果 d=1,那 μ(d)=1
  2. 如果 d=p1p2...pkpi 是互不相同的素数,那就会有 μ(d)=(1)k
  3. 如果不满足上面两个条件,那 μ(d)=0

如何求

首先,我们可以很明显的看出用定义法求会比较慢,尤其是要求一个区间的。

我们可以考虑用类似 DP 的方法求。

看到跟素数有关,自然想到先用欧拉筛求。
然后我们可以考虑,在欧拉筛枚举最小质因子的时候,我们可以想到你处理 i×primej 这个数。
那我们想 primejμ 值已经求出,那我们可以看这个最小的质因子是否已经是 primej 的因子(就是能否整除)。
如果能整除,那就说明这个质数有两个,那就直接 μi×primej=0,那否则就是多一个素数,就是 μi×primej=μi×1

你会想,啊,如果原来 primej 已经有一个素数能分解出两个或以上呢?
那因为这样,它的 μ 值就一定是 0,就算乘了 1,也还是 0,就没有问题了。

一些性质

  1. d|nμ(d)={1  n=10  n>1
  2. φ(n)=d|nμ(d)×nd

关于这道题

我们考虑利用第一条性质:
ans=i=1nj=1m[gcd(i,j)=1]

ans=i=1nj=1m[gcd(i,j)=1]=i=1nj=1md|gcd(i,j)μ(d)=i=1nj=1md|i,d|jμ(d)=di=1,d|inj=1,d|jmμ(d)=dμ(d)ndmd

然后这就是 O(n) 的,但是因为它是多次询问,每个询问都是 O(n),就还是过不了。

然后看到向下取整,自然想到整除分块。
那就前缀和 μ 函数,然后就整除分块处理就好了。

代码

#include<cstdio> #include<iostream> #define ll long long using namespace std; ll T; ll n, m; ll miu[100001], prime[100001]; ll qz[100001]; bool np[100001]; void get_miu() {//同欧拉筛预处理 μ miu[1] = 1; for (int i = 2; i <= 100000; i++) { if (!np[i]) { prime[++prime[0]] = i; miu[i] = -1; } for (int j = 1; j <= prime[0] && 1ll * i * prime[j] <= 100000ll; j++) { np[i * prime[j]] = 1; if (i % prime[j] == 0) { miu[i * prime[j]] = 0; break; } else miu[i * prime[j]] = miu[i] * -1; } } } void get_qz() {//前缀和 for (int i = 1; i <= 100000; i++) qz[i] = qz[i - 1] + miu[i]; } void work(int n, int m) { ll ans = 0; for (int l = 1; l <= n && l <= m; ) { int r = min(n / (n / l), m / (m / l));//数论分块加速 ans += (qz[r] - qz[l - 1]) * (n / l) * (m / l); l = r + 1; } printf("%lld\n", ans); } int main() { get_miu(); get_qz(); scanf("%lld", &T); while (T--) { scanf("%lld %lld", &n, &m); work(n, m); } return 0; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/YBT_JPDH_8-7-1.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(42)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示