CF1900D - Small GCD 题解

1900D - Small GCD

给定序列 A,定义 f(a,b,c)a,b,c 中最小的次小的数的 gcd,求:

i=1nj=i+1nk=j+1nf(ai,aj,ak)

题解

目前来说有两种方法,都十分有启发意义,但是有共同的开头。

考虑到 A 的顺序实际上没有什么关系,那么可以先对 A 排序,使得 i<j<kaiajak,那么此时 f(ai,aj,ak)=gcd(ai,aj),从而答案的式子转化为:

i=1nj=i+1ngcd(ai,aj)×(nj)

接下来衍生出了两种做法。

莫比乌斯反演

准确来说应该是欧拉反演?

注意到:

d|xφ(d)=x

那么我们可以将目标表达式写为:

i=1nj=i+1nd|ai,ajφ(d)×(nj)

交换求和顺序,可以得到:

d=1Vφ(d)i=1nj=i+1n[d|ai][d|aj](nj)=d=1Vφ(d)j=1n(nj)[d|aj]i=1j1[d|ai]

考虑对于每一个 d,我们将满足 d|ai 的数提出来作为集合 S,那么这是容易 O(|S|) 求解的。

接下来我们只需要考虑其复杂度了。

注意到可以认为 τ(n)=O(n)(在本题值域限制中 τ(n)128)也就是说每一个数至多在 128S 中出现,那么我们可以知道 |S|=O(128n),于是复杂度是合理的。

τ(n) 表示 n 的因数个数。

提前预处理一下因数的分解,这是 O(VlogV) 的(由于值域很小,可以直接保存),以及 φ 即可。

总复杂度是 O(VlogV+128n+nlogn)

另类的枚举

回到基础的式子:

i=1nj=i+1ngcd(ai,aj)×(nj)

如果我们固定 j,发现 gcd(ai,aj) 一定是 aj 的因数,并且 aj 的因数至多有 128 个,这启发我们可以直接枚举 d=gcd(ai,aj) 来算 d×cntj×(nj)

考虑 cntj 该如何计算,可以开一个桶 butx 记录有多少个满足 x|ai 的数。

发现并不能简单的 cntj=butd,因为这样会算重,考虑将会算重的部分给去掉。

具体来说,发现如果满足 d|gcd(ai,aj),那么 x|dx|gcd(ai,aj),也就是说 d 会在其所有因数再算一次。直接容斥有点麻烦,所以考虑直接在 but 上进行修改,也就是说进行 z|d,butzbutzbutd 的操作。

注意需要还原 but

于是就不需要容斥了……接着分析复杂度。

枚举因数同上,是 O(128n) 的,然而我们多了一个修改桶的操作,发现与枚举所有数的因数的复杂度是类同的,所以是 O(mlogm) 的。

怎么感觉复杂度假了?

总复杂度就是 O(128n+mlogm+nlogn),差不多。

总结

我首先想到的就是第一种做法……QwQ,数学学多了,但是仍然想了比较长的时间,基础还是不太牢。

第二种做法相对来说要亲民一些,但是比较难拓展到其他题上,也没法析出新的模型(因为完全可以被第一种方法取代),所以说第二种方法惜败。

最后再回顾一下莫反的 5 个经典套路:

  • [x=1]=d|xμ(d)
  • x=d|xφ(d)
  • 整除分块
  • 枚举 gcd
  • 枚举 T=kd
posted @   jeefy  阅读(79)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示