线性筛与莫比乌斯反演

线性筛与莫比乌斯反演

和上篇文章一样,一直没有研究这个东西,结果又考了GG……TAT
下定决心学一学,搞好这个东西。

线性筛

筛质数有很多方法,好像很厉害的有洲阁筛 O(n34logn) 、杜教筛 O(n23)然而我都不会QAQ)(其实这些不是用来筛素数的2333 用来筛积性函数前缀和的 Update 201849 ),还有暴力筛(就是枚举一个数的倍数)复杂度是 O(nlnn) 的。
我只学了比较简单而且实用的线性筛法。
这种筛法是避免一个数被重复筛几遍,所以效率均摊下来可以达到线性。(网上有证明)

代码

const int N = 100000; bool is_prime[N+100]; int prime[N], cnt = 0; void find_prime() { Set(is_prime, true); is_prime[0] = is_prime[1] = false; For(i, 2, N) { if (is_prime[i]) prime[++cnt] = i; For(j, 1, cnt) { if (i * prime[j] > N) break; is_prime[i * prime[j]] = false; if (i % prime[j] == 0) break; //here } } }

讲解

这个代码有一个关键点 就是上面的here 这个意义就是对于一个合数m可以分解为m=p1r1...pnrn其中
pi为质数,那么我们筛m的时候之前把p1筛掉了,所以在枚举i的时候。

  1. 如果i为素数没问题,直接向后继续推(因为筛出的质数都类似m=p1p2的形式,所以不可能重复)。
  2. 如果为合数,那么i可以分解成i=p1r1...pnrn形式其中p1pn是递增的,
    那么p1是最小的那个质数。imodp1=0的时候,就不用继续枚举了,所以我们就只能筛出不大于p1的质数i

复杂度好像是线性的( O(n) ),我不太会证复杂度。。

莫比乌斯反演

莫比乌斯反演很多时候都能大大简化运算……

定理

F(n)f(n)是定义在非负整数集合上的两个函数,并且满足条件F(n)=d|nf(d)。那么我们就能得到结论:

f(n)=d|nμ(d)F(nd)

在上面的公式中有一个μ(d)函数(莫比乌斯函数),它的定义如下:

  1. d=1,那么μ(d)=1

  2. d=p1p2...pkpi均为互异质数,那么μ(d)=(1)k。这个我的理解就是d的质因数个数为偶数的话,那么μ(d)=1 否则为 1

  3. 其他情况下μ(d)=0这个就是对上面那条的拓展了,就是指的d没有一个平方因子,或者说没有一个质因子的次数大于 1

代码

const int N = 100100; bool is_prime[N+100]; int mu[N+100] = {0, 1}, cnt = 0, prime[N+100]; void init() { Set(is_prime, true); is_prime[1] = false; For (i, 2, N) { if (is_prime[i]) { prime[++cnt] = i; mu[i] = -1; //质数的质因子个数肯定为奇数个就是1 } For (j, 1, cnt) { if (i * prime[j] > N) break; is_prime[i * prime[j]] = false; if (i % prime[j]) mu[i * prime[j]] = -mu[i]; //多了一个质因子直接变为原来结果的相反数 else { mu[i * prime[j]] = 0; //这个将要被筛的数至少具有两个prime[j]的因子 break; } } } }

常见的定理

然后还要提一下的就是一些常见的定理,证明嘛……

一般都是先分解质因数,然后再根据组合数性质去算,比如第一个。

要么就是对于一些常见的反演格式进行反演,比如第二个。

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

d|nμ(d)d=φ(n)n

莫比乌斯反演的证明

证明:

d|nμ(d)F(nd)=d|nμ(d)d|ndf(d)=d|nf(d)d|ndμ(d)=f(n)

Q.E.D


一些例题(难题)

Luogu 【P1829】[国家集训队]Crash的数字表格

题意

i=1ni=1mlcm(i,j) (n,m107)

题解

一个莫比乌斯反演然后化式子。

ans=i=1ni=1mlcm(i,j)=i=1ni=1mijgcd(i,j)=d=1min(n,m)di=1ndj=1mdij [gcd(i,j)=1]

这个就是一个更换枚举相的操作了,是个套路。你先枚举所有可能的gcd再计算这种gcd的贡献。

比如前面的那个d就是我们枚举的gcd,后面所有可能的数对,就是在ndmd中的所有互质的数对的乘积在乘上d

这个可以简单理解一下,就是两个数分别除以他们的最大公因数,然后两个数肯定是互质的。
但其对于答案的贡献就多除以了一个d,所以要乘回来。

ans=d=1min(n,m)di=1ndj=1mdx|gcd(i,j)μ(x)ij

这个就是运用了前面的公式d|nμ(d)=[n=1]来替代了[gcd(i,j)=1]的条件。(这个就是套路了)

然后我们继续推:

ans=d=1min(n,m)dx=1min(nd,md)μ(x) x2i=1ndxij=1mdxj

这个也是套路,把x提前了。就是改成了枚举x看看它的对于答案的贡献是多少。

很容易发现,就是在[1,ndx]中的所有数乘上x

就是原来可行的i。然后我们就可以根据这个来优化了。

前面那两个就是O(nln n)(令n=min(n,m))的复杂度。(就是调和级数 H(n)=i=1nni)。后面的那两个,直接用等差数列求和公式O(1)算。

但这个仍然过不去...(O(1.66108)mod的常数还很大)所以就需要来用套路的整除分块了。
就是把后面两个 很多一样答案的地方一起处理掉,所以对于那个 μ(x) x2 还要记一个前缀和。

总复杂度O(i=1nni)=O(pass)这个我也不会算。。会算的大佬私聊啊23333

代码

#include <bits/stdc++.h> #define For(i, l, r) for (int i = (l), _end_ = (int)(r); i <= _end_; ++i) #define Fordown(i, r, l) for (int i = (r), _end_ = (int)(l); i >= _end_; --i) #define Set(a, v) memset(a, v, sizeof(a)) using namespace std; bool chkmin(int &a, int b) { return b < a ? a = b, 1 : 0; } bool chkmax(int &a, int b) { return b > a ? a = b, 1 : 0; } void File() { #ifdef zjp_shadow freopen("P2154.in", "r", stdin); freopen("P2154.out", "w", stdout); #endif } typedef long long ll; ll n, m; const int N = 1e7 + 1e3; const ll Mod = 20101009, inv2 = (Mod + 1) / 2; bool is_prime[N]; int mu[N], prime[N], cnt; ll sum[N]; inline void add(ll &x, ll y) { x = ((x + y) % Mod + Mod) % Mod; } void Get_Mu(int maxn) { int res; Set(is_prime, true); is_prime[0] = is_prime[1] = false; mu[1] = 1; For(i, 2, maxn) { if (is_prime[i]) prime[++cnt] = i, mu[i] = -1; For(j, 1, cnt) { if ((res = i * prime[j]) > maxn) break; is_prime[res] = false; if (i % prime[j]) mu[res] = -mu[i]; else { mu[res] = 0; break; } } } For(i, 1, maxn) add(sum[i], sum[i - 1] + (ll)mu[i] * i * i % Mod); } inline ll fsum(ll x) { return x * (1 + x) % Mod * inv2 % Mod; } int main() { File(); cin >> n >> m; Get_Mu(min(n, m)); ll ans = 0; For(d, 1, min(n, m)) { int n_ = n / d, m_ = m / d; For(x, 1, min(n_, m_)) { if (!mu[x]) continue; int Next = min(n_ / (n_ / x), m_ / (m_ / x)); add(ans, 1ll * d * (sum[Next] - sum[x - 1]) % Mod * fsum(n_ / x) % Mod * fsum(m_ / x) % Mod); x = Next; } } cout << ans << endl; return 0; }

BZOJ3994 [SDOI2015]约数个数和

题意

i=1nj=1md(ij)d(x)表示x的约数个数。(n,m105)

题解

一个反演。当初数学一本通没看懂,真是本垃圾书

肖大佬博客一看就懂了2333。只是中间有一步化[gcd(i,j)=1]还是习惯变两步,容易理解些QwQ


__EOF__

本文作者zjp_shadow
本文链接https://www.cnblogs.com/zjp-shadow/p/8338033.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zjp_shadow  阅读(1708)  评论(3编辑  收藏  举报
编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示