数学的恶魔
莫比乌斯反演
前置知识:数论分块
基本内容
数论分块可以快速计算一些含有除法向下取整的和式(即形如 的式子),只要可以在 内计算 或已经预处理出 的前缀和时,可以用数论分块在 的时间内计算答案。
它主要利用富比尼定理(Fubini’s theorem)(听起来很厉害但其实没啥):我们注意到,有很多的 它们的 是相等的,这启发我们分段打包计算。
举个例子,如果我们要求 ,我们发现很多 都对应同一个 ,它们呈块状分布,在打个表仔细观察(证明),发现对于每一个块,它的最后一个数是 ( 自带下取整,一般我们称其这个式子的值为 ),如对于 ,分成: ,其中 ,于是,求解上述问题的代码如下(常用):
UVA11526 H(n)
1 2 3 4
| for(int l=1,r;l<=n;l=r+1){ r=n/(n/l); ans+=(r-l+1)*(n/l); }
|
类似的,我们可以用 计算 那么只要 好求我们就能算了!
一般而言,我们可以统计一个前缀和(积),因为每当我们使用分块跳过一个区间的时候,其所对应的函数值也跳过了一个区间。所以此时,就需要乘上那一个区间的函数值(可以自己举个例子理解一下)。
但要是不一般(比如 的前缀和都会TLE),那……去死吧!(dalao可以用杜教筛试一试)
引理一
证明:
引理二
其中 表示集合 的元素个数
该引理是时间复杂度的保证
证明:
对于 ,由于 最多有 种不同取值, 也只有 种取值
对于 ,则 ,也只有 种取值
前置知识:积性函数
若函数 为积性函数,则有
可以证明以下函数全是积性函数:
- -欧拉函数
- -莫比乌斯函数
- -最大公因子,当k固定的情况
- -正因子数目
- -所有正因子之和
- -因子函数, 的所有正因子的 次幂之和,当中 可为任何复数
- -不变的函数,定义为 (完全积性)
- -单位函数,定义为 (完全积性)
- -幂函数,对于任何复数、实数 ,定义为 (完全积性)
- -定义为:若 , ;若 , 。别称为“对于狄利克雷卷积的乘法单位”(完全积性)
- -刘维尔函数,关于能整除 的质因子的数目
- ,定义为 ,在此加性函数 是不同能整除n的质数的数目
- 另外,所有狄利克雷特征均是完全积性的
积性函数一般可以用线性筛筛得
前置知识:狄利克雷卷积
定义
狄利克雷( )卷积定义为:
对于两个数论函数 ,有:
性质
狄利克雷卷积满足以下运算律:
- 交换律:
- 结合律:
- 分配律:
- ,其中 为狄利克雷卷积的单位元
for example:
求法
对于卷积的第 项可以直接枚举约数,在 的时间内计算
但是对于狄利克雷卷积的前 项
如果一项一项算的话就需要 ,但实际上可以优化
设 分别枚举 对于 即可。
时间复杂度
1 2 3
| for(int i=1;i<=n;++i) h[i]=0; for(int i=1;i<=n;++i) for(int j=1;j*i<=n;++j) h[i*j]=(h[i*j]+f[i]*g[j]%P)%P;
|
对于 次卷积,还可以快速幂优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| struct HanShu{ int f[N]; void inint(){ for(int i=1;i<=n;++i) f[i]=0; } HanShu operator*(const HanShu _t){ HanShu h; h.inint(); for(int i=1;i<=n;++i) for(int j=1;j*i<=n;++j) h.f[i*j]=(h.f[i*j]+f[i]*_t.f[j]%P)%P; return h; } void operator=(const HanShu _t){ for(int i=1;i<=n;++i) f[i]=_t.f[i]; } }; HanShu H_qpow(HanShu x,int y){ HanShu res; int _t=0; while(y){ if(y&1){ ++_t; if(_t==1) res=x; else res=res*x; } x=x*x; y>>=1; } return res; }
|
前置知识:莫比乌斯函数
定义
设正整数 分解质因数为 ,定义函数
我们称 为莫比乌斯( )函数。
性质和结论
筛法
若只求一项莫比乌斯函数,分解质因数即可;
若要求多项可以在线性筛素数时一并求出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| int primes[N],mu[N],cnt=0; bool v[N]; mu[1]=1; for(int i=2;i<=n;++i){ if(!v[i]) primes[++cnt]=i,mu[i]=-1; for(int j=1;j<=cnt&&primes[j]*i<=n;++j){ v[primes[j]*i]=true; if(i%primes[j]==0){ mu[i*primes[j]]=0; break; } mu[i*primes[j]]=-mu[i]; } }
|
正题:莫比乌斯反演
定理
若 和 是定义在 上的函数,且有
则:
我们称该定理为莫比乌斯反演定理(原来是用 表示 ,现在反过来了)。
证明如下:
当然,不难发现,狄利克雷卷积也可以证明:
然而,在莫比乌斯反演中更常用的是这个式子:
当 和 满足:
则有:
其实就是枚举约数变成了枚举倍数,若用卷积证明,过程都一样,下面用定义证明:
以上就是莫比乌斯反演的常用形式(要记住)
例题一
Problem b
分析:
若我们将数对 对应到坐标系内,其实是要求在左下角为 右上角为 的矩形中有多少个点 满足
由矩形考虑二维前缀和,设 表示以 为右上角, 为左下角的矩形中满足要求的点的个数,则
考虑用莫比乌斯反演,我们需要定义 且让 (即满足反演条件),要注意的是, 应该是比较好计算的函数(至少要比 好计算,不然你反演用 表示 干嘛),还有就是 要与答案有关系(不然求出 干嘛)
那么,不妨令 则我们要求的 就是
又因为 ,故定义
反演得:
又因为由 的定义, ,故
所以
枚举倍数比枚举因数要简单,所以我们令 ,原式化为 预处理 的前缀和后用数论分块可以求
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| #include<bits/stdc++.h> #define LL long long using namespace std; const int N=50000+5; int pri[N],cnt,mu[N]; bool v[N]; LL sum[N]; void pred(){ mu[1]=1; for(int i=2;i<N;++i){ if(!v[i]) pri[++cnt]=i,mu[i]=-1; for(int j=1;j<=cnt&&pri[j]*i<N;++j){ v[pri[j]*i]=true; if(i%pri[j]==0){ mu[i*pri[j]]=0; break; } mu[i*pri[j]]=-mu[i]; } } sum[0]=0; for(int i=1;i<N;++i) sum[i]=sum[i-1]+mu[i]; } int g(int k,int x){ return k/(k/x); } LL S(int x,int y,int k){ LL res=0; x=x/k,y=y/k; int _t=min(x,y); for(int l=1,r;l<=_t;l=r+1){ r=min(_t,min(g(x,l),g(y,l))); res+=(sum[r]-sum[l-1])*(x/l)*(y/l); } return res; } int main(){ int n; int a,b,c,d,k; pred(); scanf("%d",&n); while(n--){ scanf("%d%d%d%d%d",&a,&b,&c,&d,&k); printf("%lld\n",S(b,d,k)-S(a-1,d,k)-S(b,c-1,k)+S(a-1,c-1,k)); } return 0; }
|
例题二
约数个数和
分析:
先看原题给我们的函数,由于 是积性函数(忘了的上去翻),所以有 (呃呃呃,鬼想的到啊!)
简单证明:
设 ,则 ,由约数个数定理
再看等式右边, 的因数也一定是在 中,对于质因子 ,若 含 ,则有意义的 一定不含 ,此时 有 种取值,同理,若 含 有 种取值,加上一种 都不含 ,共 种有意义的取值,类似的,讨论其它质因数,乘法原理得左式等于右式
QED
我们发现 ,类似与上题,令: 那么
则 ,反演得
故 ,为了求出 ,我们来看 ,由于 与 无关,我们可以枚举 计算有多少 符合要求,
同上题一样,可设 ,得
调换循环顺序得
令 ,有 ,故 ,预处理所有的 ,数论分块解决
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| #include<bits/stdc++.h> #define LL long long using namespace std; const int N=50000+5; int pri[N],cnt,mu[N]; LL sum[N],h[N]; bool v[N]; int g(int k,int x){ return k/(k/x); } void pred(){ mu[1]=1; for(int i=2;i<N;++i){ if(!v[i]) pri[++cnt]=i,mu[i]=-1; for(int j=1;j<=cnt&&pri[j]*i<N;++j){ v[pri[j]*i]=true; if(i%pri[j]==0){ mu[i*pri[j]]=0; break; } mu[i*pri[j]]=-mu[i]; } } sum[0]=0; for(int i=1;i<N;++i) sum[i]=sum[i-1]+mu[i]; for(int i=1;i<N;++i) for(int l=1,r;l<=i;l=r+1){ r=min(i,g(i,l)); h[i]+=(r-l+1)*(i/l); } } int main(){ pred(); int T; scanf("%d",&T); while(T--){ int n,m; scanf("%d%d",&n,&m); LL ans=0; int _t=min(n,m); for(int l=1,r;l<=_t;l=r+1){ r=min(_t,min(g(n,l),g(m,l))); ans+=(sum[r]-sum[l-1])*h[n/l]*h[m/l]; } printf("%lld\n",ans); } return 0; }
|
例题三
龙哥的问题
本题比较简单,甚至不用莫反
考虑答案:
对于每一个 枚举约数( 范围内最多的一个数约数只有 多个) ,再 求 ,本题的范围可以通过
那可否继续优化呢?令 ,再继续化一下:
其中 ,又因为 ,不妨设 ,下面的变化比较常用,要记住:
如果不理解的话可以这样想:每个括号里取一项作 乘起来刚好对应一个
那么考虑上式对答案的贡献,我们发现对于所有的 ,若取第 个质因子的指数为 (即取 )对答案贡献为 ,其他情况(即 )对答案的贡献都是 ,故答案可化为:
分解 的质因子,可以在 时间求解,但注意,本题对 卡的非常死,甚至在计算 时先乘再除都会炸
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include<bits/stdc++.h> #define LL long long using namespace std;
int main(){ LL n; scanf("%lld",&n); LL ans=n; for(LL i=2;i*i<=n;++i) if(n%i==0){ LL a=0; while(n%i==0) ++a,n/=i; ans/=i,ans*=i+a*i-a; } if(n>1) ans/=n,ans*=n+n-1; printf("%lld\n",ans); return 0; }
|
例题四
来看一道难题:
数字表格
这道题要点很杂很多,我们慢慢看:
首先明确
其中 表示Fibonacci数列第 项
由于 不好处理,考虑枚举 :
其中 ,
发现这个 的定义我们非常熟悉,于是一通莫反:
此时我们发现在莫反后, 可以在 内计算,但是,我们观察 ,对于每个 , 都必须重新计算(因为 与 都有关),这样的时间负责度为 直接爆炸好吧
必须考虑优化,但 和 都太不好搞了, 也不好在变形(我在这里卡了好久),既然两个都不好搞,我们不妨把它们一起来看看:
我们发现一个关键点:每次询问只修改 而 对于每次询问来说是一样的,这启发我们把几个与 无关的项拿出来预处理:
发现指数位置的 可以数论分块,而 可以提前预处理出前缀积,如此,回答询问的总时间变为
下面考虑如何计算 ,由于因数不好枚举,我们改为枚举倍数:
预处理时间为
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| #include<bits/stdc++.h> #define LL long long using namespace std; const int N=1000000+5,P=1000000000+7; LL pri[N],cnt,mu[N],fb[N],h[N],inv_fb[N],mul[N]; bool v[N]; int g(int k,int x){ return k/(k/x); } LL qpow(LL x,LL y){ LL res=1; while(y){ if(y&1) res=(res*x)%P; x=(x*x)%P; y>>=1; } return res; } void pred(){ mu[1]=1; for(int i=2;i<N;++i){ if(!v[i]) pri[++cnt]=i,mu[i]=-1; for(int j=1;j<=cnt&&pri[j]*i<N;++j){ v[pri[j]*i]=true; if(i%pri[j]==0){ mu[i*pri[j]]=0; break; } mu[i*pri[j]]=-mu[i]; } } fb[0]=0; fb[1]=1; inv_fb[1]=1; for(int i=2;i<N;++i) fb[i]=(fb[i-1]+fb[i-2])%P,inv_fb[i]=qpow(fb[i],P-2); for(int i=1;i<N;++i) h[i]=1; for(int i=1;i<N;++i){ if(!mu[i])continue; for(int j=1;j*i<N;++j) h[j*i]=h[j*i]*(mu[i]==1?fb[j]:inv_fb[j])%P; } mul[0]=1; for(int i=1;i<N;++i) mul[i]=(mul[i-1]*h[i])%P; } LL get_ans(int x,int y){ LL res=1,as; int _t=min(x,y); for(int l=1,r;l<=_t;l=r+1){ r=min(_t,min(g(x,l),g(y,l))); as=mul[r]*qpow(mul[l-1],P-2)%P; res=res*qpow(as,1ll*(x/l)*(y/l)%(P-1))%P; } return res; } int main(){ pred(); int T; scanf("%d",&T); int n,m; while(T--){ scanf("%d%d",&n,&m); printf("%lld\n",get_ans(n,m)); } return 0; }
|
补充:变换方法
下面记录一些常见的变换方法: