莫比乌斯反演专题训练及解答总结
需要掌握的两个公式:
公式1:
公式2:
重点掌握常用的第二个公式。
至于相关证明和结论,参见吉大附中的PDF文档,写的真的挺好的,给个赞!
http://7xqmhv.com1.z0.glb.clouddn.com/%E8%8E%AB%E6%AF%94%E4%B9%8C%E6%96%AF%E5%8F%8D%E6%BC%94.pdf
想要学会莫比乌斯反演之前呢?先要学会素数线性筛及莫比乌斯函数的求法,关于素数线性筛和莫比乌斯函数的求法,可以自己百度,也可以看看PDF文档中的求莫比乌斯函数相关知识点。
掌握了莫比乌斯反演这两个公式之后,我们能干啥呢?请看以下例题:
入门典例:(可参见博客:https://blog.sengxian.com/algorithms/mobius-inversion-formula ) (百度居然搜索不到这篇博客,写的这么好,质量这么高)
入门例题
典例1: 令1<= i <=n, 1<= j<=m,求GCD(x,y)=K的个数(OJ题目: HDU 1695)。
直接能想到的就是枚举每个i,j,来求结果,算法复杂度为 O(n2) ,如果这样解,等着去超时吧!
接下来我们看看,利用莫比乌斯反演是怎么将这个复杂度给降下来的!
这个时候,我们设g(x)为gcd(i,j)=x的个数,f(x)为x|gcd(i,j)的个数,那么:
而 f(x)=x|gcd(i,j) 的个数为 ⌊nx⌋∗⌊mx⌋ 。
(如何证明呢? 如果gcd(i,j)为x的倍数,那么i和j一定为x的倍数,对于所有的i,那么 ⌊ix⌋∈(1,⌊nx⌋) ,同理可证明j,对于所有 1≤i′≤n/x , 1≤j′≤m/x ,组成的所有(i’ ,j’ ),各自扩大x倍之后,即为所有的 x|gcd(i,j) 的对数,结论得证)
所以我们得到:
这个时候,如果线性时间内求出了莫比乌斯函数,你就会神奇的发现,这道题所要求的 g(K) 的复杂度降到了 O(n) ,莫比乌斯反演的魅力出来了, 有木有!
然而,你会发现, ⌊nd⌋ 出现了很多重复的结果(例如 ⌊74⌋ , ⌊75⌋ , ⌊76⌋ 等结果都一样),如果我们枚举 ⌊nd⌋ 的结果,就会发现n/d的结果最多只会有 2∗n√ 个。
(证明:对于所有的
⌊ni⌋
(1<= i <=n )的结果,只会有
2∗n√
个:
对于所有的
1≤i≤n√
,由于i只有
n√
个取值,那么结果也只可能有
n√
个;
对于所有的
n√≤i≤n
,
1≤⌊ni⌋≤n√∈(1,n√)
之间,所以结果也只可能有
n√
个;
综上,结论得证。
)
所以,我们如果枚举n/d的结果的话,求得u的前缀和的话,就可以将复杂度从O(n)降到
O(n√)
,关于分块的思想(分块的概念就是说存在连续的i,使得n/i的结果一样,这些连续的i为一块),可以看我推荐的那篇博客。
如何枚举分块呢? 设 ⌊ni⌋ 为一个分块里面的结果的话,那么和它相等的末尾的那个i即为 ⌊n⌊ni⌋⌋ ,如果实在想不明白,证明见推荐的那篇博客。
详细实现步骤,见推荐博客吧!
——————以下为转载内容———————-
例:n = 32, m = 40, k = 2n=32,m=40,k=2,红色为相等的段。
对于相等的段,我们求取
μ
的前缀和,即可批量计算这一个段的答案。
ll F(int n, int m, int d) {
if (n > m) swap(n, m);
ll ans = 0;
n /= d, m /= d;
for (int i = 1, last = 1; i <= n; i = last + 1) {
last = min(n / (n / i), m / (m / i));
ans += (ll)(sum[last] - sum[i - 1]) * (n / i) * (m / i);
}
return ans;
}
对于位置 i,找到下一个相等的位置的代码为 min(n/(n/i),m/(m/i)) 。对于 n 来说,我们要找到最大的 j,满足:
⌊nj⌋≥⌊ni⌋
可以拆掉左边的底:
nj≥⌊ni⌋
继续化简:
j≤⌊n⌊ni⌋⌋
所以
j=⌊n⌊ni⌋⌋
,对 m同理,取两个j 最小值即为都不变的段 [i, j][i,j]。
总之,复杂度为
O(n√+m−−√)
。
这个
f(k)
以及分块非常重要,几乎题题都会用到。
——转载结束———-
例2:设a<=i<=b, c<=j<=d,求gcd(i,j)=k的个数(BZOJ 2301)
这是典例1的变形,只需要用容斥原理求出来即可:我们设典例1中的结果为ans(n,m),那么这题的答案就为ans(b,d)-ans(a-1,d)-ans(c-d,b)+ans(a-1,c-1)。这里我们必须要使用典例1中的最后优化为复杂度 O(n√) 的算法,不然会超时。
例3:求有多少数对(x,y)(1<=x<=n,1<=y<=n)满足gcd(x,y)为质数(BZOJ 2818)
在典例1,例2的基础上,我们可以得到:
令T=i*p,那么有:
我们这时候想到,如果把 ∑p|Tμ(Tp) 想办法先求出来,然后求出它的前缀和,那么就能将它的复杂度降到原来的 O(n√) 了。我们令
if (T%p’==0) :
sum1(T∗p′)=μ(T) (没耐心写公式了,自己放到sum1(T)的那一串式子中去试,很简单的啦!此处别忘了break)
else :
sum1(T∗p′)=μ(T)−sum1(T)
以上求sum1的方法是不是可以放到素数筛里面去呢?嘿嘿(PS,这种递推的思想很重要,要掌握啊!)!
最后对sum1求个前缀和,便可以利用分块思想在 O(n√) 的复杂度里求出结果了。
接下来给两个进阶题
例4:给出n,m,求
∑ni=1∑mj=1lcm(i,j)
(BZOJ 2154)
首先,对于i,j的最小公倍数,我们有
lcm(i,j)=i∗jgcd(i,j)
,所以有
if(T%p==0) :
MyF(T*p)=MyF(T)
else:
MyF(T*p)=(1-p)MyF(T)
这个时候我们就将这个后缀和能以O(1)时间复杂度算出来了,可以再考虑分块优化,就能将计算复杂度降到 O(n√) 了。
最后,等等,为啥我预处理了所有数据范围后超时啊?嘿嘿,因为BZOJ算的时限为所有测试的总时限,将预处理的规模变为min(n,m)+233的规模就行了。最后5s+过了。
例5:给出n,求 f(n)=∑ni=1∑ij=1⌈ij⌉[(i,j)=1] (HDU 6134)
先把f(n)画张表出来,有:
设:g(x)为gcd(i,j)=x的所有 ⌈ij⌉ 之和
f(x)为x|gcd(i,j)的所有 ⌈ij⌉ 之和
则有:
这个时候,对于f(x)=x|gcd(i,j)我们可以将i,j缩小x倍,n对应缩小到了n/x,所以f(d)为表中前n/d列的所有数取上整之和,观察数表,我们发现从第i列到第i+1列,只是加上了第i列中i的因数个数+1,我们将这个规律放到素数筛的递推关系中去求F(T)(F(T)代表T的因数的个数,有点抽象,自己好好想想):
if(T%p==0):
F(T*p)=2*F(T)-F(T/p) (别忘了break)
else:
F(T*p)=2*F(T)
最后用个for循环到1e6递推出所有的 f1(x) ( f1(x) 代表数表中第x列之和)即可,最后,得到
还有这个题
菜菜的我能力还不够,还没做的题:BZOJ 3529 数表
—–end—–