zoukankan      html  css  js  c++  java
  • 莫比乌斯反演学习笔记

    基本公式

    莫比乌斯函数

    原式子

    [mu(u)= egin{cases} 1&n=1\ 0&n ext{含有平方因子}\ (-1)^k&k ext{为}n ext{的本质不同的质因子个数}\ end{cases} ]

    代码实现(线性筛)

    void get_mu(){
    	mu[1]=1;
    	for(int i=2;i<=n;i++){
    		if(!flag[i]) prime[++tot]=i,mu[i]=-1;
    		for(int j=1;j<=tot&&prime[j]*i<=n;j++){
    			flag[prime[j]*i]=1;
    			if(i%prime[j]==0){
    				mu[i*prime[j]]=0;
    				break;
    			}
    			mu[i*prime[j]]-=mu[i];
    		}
    	}
    }
    

    奇妙性质({color{#8B0000}{large star}})

    [sum_{dmid n} mu(d) =varepsilon(n)= egin{cases} 1&n=1\ 0 &n eq 1\ end{cases} ]

    (因此运用到题里后经常需要对莫比乌斯函数求前缀和)

    莫比乌斯反演({color{#8B0000}{large star}})

    [ ext{如果有} f(n)=sum_{dmid n} g(d) ext{ 则} g(n)=sum_{dmid n} mu(d) f( frac{n}{d}) ]

    [ ext{如果有} f(n)=sum_{nmid d} g(d) ext{ 则} g(n)=sum_{nmid d} mu( frac{n}{d}) f(d) ]

    运用技巧

    数论分块({color{#8B0000}{large star}})

    解说

    [ ext{有时我们会遇到形如} sum lfloor frac{n}{i} floor ext{的式子且} Theta (n) ext{过不去,这时候就会用到数列分块。} ]

    [ ext{显然对于一段数} lfloor frac{n}{i} floor ext{都是相等的,那么我们就可以将其分到一块里一并计算。} ]

    [ ext{那么现在我们就需要找到一个最大的}j ext{使得} lfloor frac{n}{i} floor = lfloor frac{n}{j} floor ext{,结论是此时} j= left lfloor frac{n}{left lfloor frac{n}{i} ight floor } ight floor ext{。下面是证明。} ]

    [egin{aligned} &leftlfloorfrac{n}{i} ight floor leq frac{n}{i}\ implies &leftlfloorfrac{n}{ leftlfloorfrac{n}{i} ight floor } ight floor geq leftlfloorfrac{n}{ frac{n}{i} } ight floor = leftlfloor i ight floor=i \ implies &ileq leftlfloorfrac{n}{ leftlfloorfrac{n}{i} ight floor } ight floor=j\ &&square end{aligned} ]

    此时时间复杂度为(Theta(sqrt n))

    当然还有二维版本的

    [sum_{i=1}^{min (n,m)}leftlfloorfrac{n}{i} ight floorleftlfloorfrac{m}{i} ight floor ]

    此时将r = n/(n/l)替换成 r = min(n/(n/l), m/(m/l))就完了

    (以上证明引自OI Wiki

    代码实现

    (以洛谷P2261为例,很裸的数论分块)

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    int n,k;
    ll ans,l,r;
    int main(){
    	scanf("%d%d",&n,&k);
    	ans=(ll)n*(ll)k;
    	l=1;
    	while(l<=n){
    		if(k/l) r=min(k/(k/l),(ll)n);
    		else r=n;
    		ans-=(k/l)*(r-l+1)*(l+r)/2;
    		l=r+1;
    	}
    	printf("%lld
    ",ans);
    	return 0;
    }
    

    例题

    解说

    洛谷P2522 Problem b

    题目让我们求的是

    [sum_{i=a}^{b}sum_{j=c}^{d} varepsilon (gcd(i,j)==k)qquad ]

    可以拆成四块进行求解,每一块都形如

    [sum_{i=1}^{n}sum_{j=1}^{m}varepsilon (gcd(i,j)==k) ]

    上式可化为

    [sum_{i=1}^{lfloorfrac{n}{k} floor}sum_{j=1}^{lfloorfrac{m}{k} floor}varepsilon(gcd(i,j)) ]

    [displaystylesum_{i=1}^{lfloorfrac{n}{k} floor}sum_{j=1}^{lfloorfrac{m}{k} floor}sum_{dmid gcd(i,j)}mu(d) ]

    变换求和顺序得

    [displaystylesum_{d=1}mu(d)sum_{i=1}^{lfloorfrac{n}{k} floor}varepsilon (dmid i)sum_{j=1}^{lfloorfrac{m}{k} floor}varepsilon (dmid j) ]

    (1 sim lfloor frac{n}{d} floor)(d)的倍数一共(lfloor frac{n}{kd} floor)个,所以原式还可以化为

    [displaystylesum_{d=1}mu(d)lfloorfrac{n}{kd} floorlfloorfrac{m}{kd} floor ]

    符合数论二维分块的形式,可以 (Theta(sqrt n)) 搞掉了

    代码

    #include<bits/stdc++.h>
    using namespace std;
    const int lzw=5e4+3;
    int tot,prime[lzw],mu[lzw],a,b,c,d,t,k;
    bool flag[lzw];
    void get_mu(){
    	mu[1]=1;
    	for(int i=2;i<=lzw-3;i++){
    		if(!flag[i]) prime[++tot]=i,mu[i]=-1;
    		for(int j=1;j<=tot&&prime[j]*i<=lzw-3;j++){
    			flag[prime[j]*i]=1;
    			if(i%prime[j]==0){
    				mu[i*prime[j]]=0;
    				break;
    			}
    			mu[i*prime[j]]-=mu[i];
    		}
    	}
    	for(int i=1;i<=lzw-3;i++) mu[i]+=mu[i-1];
    }
    int solve(int n,int m){
    	int res=0,l=1,r;
        while(l<=min(n,m)){
            r=min(n/(n/l),m/(m/l));
            res+=(mu[r]-mu[l-1])*(n/l)*(m/l);
            l=r+1;
        }
        return res;
    }
    int main(){
    	get_mu();
    	scanf("%d",&t);
    	while(t--){
    		scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
    		printf("%d
    ",solve(b/k,d/k)-solve(b/k,(c-1)/k)-solve((a-1)/k,d/k)+solve((a-1)/k,(c-1)/k));
    	}
    	return 0;
    }
    

    例题2

    解说

    上面的例题似乎并没有反演,只是用了一下莫比乌斯函数的性质,这里再放一个反演的例题。

    洛谷P2257 YY的GCD

    题目让我们求的答案为

    [ans=sum_{i=1}^{N}sum_{j=1}^{M} varepsilon(gcd(i,j)==prime) ]

    不妨设

    [f(n)= sum_{i=1}^{N}sum_{j=1}^{M} varepsilon(gcd(i,j)==n) ]

    那么答案可以化为

    [ans=sum_{pin prime}f(p) ]

    再增设一个函数

    [g(n)=sum_{nmid d} f(d) ]

    显然(好像不这么显然但是稍微看看能弄明白)它还可以写成另一种形式

    [g(n)=leftlfloor frac{N}{n} ight floor leftlfloor frac{M}{n} ight floor ]

    这个时候我们就珂以反演

    [ecause g(n)=sum_{nmid d} f(d) ]

    [ herefore f(n)=sum_{nmid d} mu(frac{d}{n})g(d) ]

    那么我们就珂以接着推答案式子

    [ans=sum_{pin prime}f(p)=sum_{pin prime}sum_{pmid d} mu(frac{d}{p})g(d) ]

    [ans=sum_{pin prime}sum_{pmid d} mu(frac{d}{p})leftlfloor frac{N}{d} ight floor leftlfloor frac{M}{d} ight floor ]

    我们把后一个求和换成枚举(d)的形式,显然在(d)大于(N)(M)时后面的求和全都是(0),因此我们只枚举到(min (N,M))

    [ans=sum_{pin prime}sum_{d=kp,kin N^+}^{min(N,M)} mu(frac{d}{p})leftlfloor frac{N}{d} ight floor leftlfloor frac{M}{d} ight floor ]

    换一个枚举顺序得

    [ans=sum_{d=1}^{min(N,M)}sum_{pin prime,pmid d}mu(frac{d}{p})leftlfloor frac{N}{d} ight floor leftlfloor frac{M}{d} ight floor ]

    由于第二个求和已经和(d)无关,我们可以提一下公因式

    [ans=sum_{d=1}^{min(N,M)}leftlfloor frac{N}{d} ight floor leftlfloor frac{M}{d} ight floorsum_{pin prime,pmid d}mu(frac{d}{p}) ]

    这个时候我们就可以用数列分块搞它了。预处理的时候多处理一下(sum_{pin prime,pmid d}mu(frac{d}{p}))即可

    代码

    
    
    #include<bits/stdc++.h>
    using namespace std;
    const int lzw=1e7+3;
    typedef long long ll;
    int n,m,t,prime[lzw],tot,mu[lzw],g[lzw];
    bool flag[lzw];
    ll sum[lzw];
    void get_mu(){
    	flag[1]=1,mu[1]=1;
    	for(int i=2;i<=lzw-3;i++){
    		if(!flag[i]) flag[i]=1,prime[++tot]=i,mu[i]=-1;
    		for(int j=1;j<=tot&&(ll)prime[j]*i<=(ll)lzw-3;j++){
    			flag[prime[j]*i]=1;
    			if(i%prime[j]==0){
    				mu[i*prime[j]]=0;
    				break;
    			}
    			mu[i*prime[j]]-=mu[i];
    		}
    	}
    	for(int i=1;i<=tot;i++)
    		for(int j=1;(ll)j*prime[i]<=(ll)lzw-3;j++)
    			g[j*prime[i]]+=mu[j];
    	for(int i=1;i<=lzw-3;i++) sum[i]=sum[i-1]+g[i];
    }
    ll solve(int n,int m){
    	ll res=0;
    	int l=1,r;
    	while(l<=min(n,m)){
    		r=min(n/(n/l),m/(m/l));
    		res+=(ll)(n/l)*(m/l)*(sum[r]-sum[l-1]);
    		l=r+1;
    	}
    	return res;
    }
    int main(){
    	get_mu();
    	scanf("%d",&t);
    	while(t--){
    		scanf("%d%d",&n,&m);
    		printf("%lld
    ",solve(n,m));
    	}
    	return 0;
    }
    
    

    幸甚至哉,歌以咏志。

  • 相关阅读:
    分布式锁的几种实现方式
    分布式锁简单入门以及三种实现方式介绍
    Redis 总结精讲
    Redis 总结精讲 看一篇成高手系统-4
    Request、Response 之 Http 请求
    定时任务 Cron表达式
    跑批利器--批处理应用程序
    使用MultiByteToWideChar转换UTF8为GBK(UTF8在Windows的代码页是CP_UTF8)
    了解 XML 数字签名
    QuickReport根据每行的内容长度动态调整DetailBand1的行高
  • 原文地址:https://www.cnblogs.com/DarthVictor/p/13726829.html
Copyright © 2011-2022 走看看