Loading

莫比乌斯反演

狄利克雷卷积

1 函数定义

\[\mu(n)=1(n=1),(-1)^k(n=\prod\limits_{i=1}^kp_i),0(other)\\ I(n)=1\\ \epsilon(n)=[n=1]\\ id(n)=n \]

2 卷积定义

\[(f*g)(x)=\sum\limits_{d|n}f(d)g(\dfrac{n}{d}) \]

有一下性质:

\[f*g=g*f\\ (f*g)*h=f*(g*h)\\ (f+g)*h=f*h+g*h \]

三个等式:

\[\mu*I=\epsilon\\ \phi*I=id\\ \mu*id=\phi \]

  • 证明:

  • 第一个等式:

    我们把式子展开:\((\mu* I)(n)=\sum\limits_{d|n}\mu(d)I(\dfrac{n}{d})=\sum\limits_{d|n}\mu(d)\),根据莫比乌斯函数的定义不难发现,我们可以认为 \(n=p_1p_2...p_m\) ,即我们只保留指数的种类而非其指数,这是因为所有带指数的式子对答案都没有贡献。我们考虑每个因子选与不选两种情况,不难发现我选奇数个因子和偶数个因子的方案数都是 \(2^{m-1}\) 次方。注意上面的讨论并不包括 \(n=1\) ,所以根据莫比乌斯函数定义,当且仅当 \(n=1\) 时,卷积值才为 \(1\) 否则为 \(0\) 。定理得证。

  • 第二个等式:

    \((\phi*I)(n)=\sum\limits_{d|n}\phi(d)I(\dfrac{n}{d})=\sum\limits_{d|n}\phi(d)\),令 \(n=p_1^{m_1}p_2^{m_2}...p_k^{m_k}\),我们实际上是在枚举\(n\) 的某一个因子。根据 \(\phi(n)\) 为一个积性函数,我们可以把所有的 \(d\) 拆开,我们就可以得到 \(\sum\limits_{d|n}\phi(d)=(\sum\limits_{i_1=0}^{m_1}\phi(p_1^{i_1}))\times ...(\sum\limits_{i_k=0}^{m_k}\phi(p_k^{i_k}))\) ,不难发现 \(\sum\limits_{i_j=0}^{m_j}\phi(p_j^{i_j})=(\sum\limits_{k=1}^{m_j}p_j^{k-1})(p_j-1)=p_j^{m_j}\) ,所以得证。

  • 第三个等式:

  • 首先证明这样一个等式: \((\epsilon*f)(n)=\sum\limits_{d|n}\epsilon(d)f(\dfrac{n}{d})=\epsilon(1)f(n)=f(n)\),所以 \(\epsilon*f=f\),我们在第三个等式左边同时卷上一个 \(\epsilon\) ,得证。

3 莫比乌斯反演

\[g(n)=\sum\limits_{d|n}f(d)\Leftrightarrow f(n)=\sum\limits_{d|n}\mu(d)g(\dfrac{n}{d}) \]

  • 证明:
  • 注意到上式可以写成 \(g=f*I\Leftrightarrow f=\mu*g\),两边同时卷 \(I\) 就可以得证。

还有另一种形式:

\[ g(n)=\sum\limits_{n|d}f(d)\Leftrightarrow f(n)=\sum\limits_{n|d}\mu(\dfrac{d}{n})g(d) \]

例题1

\(\sum\limits_{i=1}^n\sum\limits_{j=1}^mgcd(i,j)\)

技巧 \(1\):增加枚举量。

\[\sum_{i=1}^n\sum_{j=1}^mgcd(i,j)=\sum_{i=1}^n\sum_{j=1}^mid(gcd(i,j))=\sum_{i=1}^n\sum_{j=1}^m\sum\limits_{d|gcd(i,j)}\phi(d) \]

技巧 \(2\):交换枚举顺序

\[\sum_{i=1}^n\sum_{j=1}^m\sum\limits_{d|gcd(i,j)}\phi(d)=\sum\limits_{d=1}^{\min(n,m)}\sum\limits_{i=1}^{\left\lfloor\tfrac{n}{d}\right\rfloor}\sum\limits_{j=1}^{\left\lfloor\tfrac{m}{d}\right\rfloor}\phi(d) \]

技巧 \(3\) :分离无关变量

\[\sum\limits_{d=1}^{\min(n,m)}\sum\limits_{i=1}^{\left\lfloor\tfrac{n}{d}\right\rfloor}\sum\limits_{j=1}^{\left\lfloor\tfrac{m}{d}\right\rfloor}\phi(d)=\sum\limits_{d=1}^{\min(n,m)}\phi(d)\sum\limits_{i=1}^{\left\lfloor\tfrac{n}{d}\right\rfloor}\sum\limits_{j=1}^{\left\lfloor\tfrac{m}{d}\right\rfloor}1=\sum\limits_{d=1}^{\min(n,m)}\phi(d)\left\lfloor\dfrac{n}{d}\right\rfloor\left\lfloor\dfrac{m}{d}\right\rfloor \]

所以我们可以 \(O(n)\) 来做这个东西。

我们现在考虑这个问题,求:

\[\sum\limits_{i=1}^n\phi(i)\times\left\lfloor\dfrac{n}{i}\right\rfloor \]

要求复杂度低于 \(O(n)\)

考虑 \(\left\lfloor\dfrac{n}{i}\right\rfloor\) 怎么算,我们采用数论分块,可以在 \(O{\sqrt n}\) 的复杂度下求上面式子的值。

原理如下:

  • \(i\) 分成 \(i\leq\sqrt n\)\(i>\sqrt n\) 来讨论,我们发现 \(\left\lfloor\dfrac{n}{i}\right\rfloor\) 只有 \(2\sqrt n\) 个取值。

    所以如果我们把 \(i\) 取值中式子的值不变的放在一个块中,实际上只有大约 \(\sqrt n\) 个块。

  • 所以我们的主要问题是确定一个 \(i\) 找到一个 \(j\) ,使得 \(i\)\(j\) 中的所有数,\(\left\lfloor\dfrac{n}{i}\right\rfloor\) 的值相同。

  • 首先可以二分,但这样复杂度不会很优秀。

  • \(x=\left\lfloor\dfrac{n}{i}\right\rfloor\) ,那么发现 \(\left\lfloor\dfrac{n}{x}\right\rfloor\) 就应该等于 \(j\) 。可以这么理解,我们实际上是要找到满足 \(j\times x\leq n\) 的最大的 \(j\) 。那么我们就可以得出 \(j=\left\lfloor\dfrac{n}{x}\right\rfloor\) 的结论。

代码:

int get_ans(int n){
    int ans=0;
    for(int i=1;i<=n;){
        int n/(n/i);
        ans+=(n/i);
        i=j+1;
    }
    return ans;
}

如果是两个变量变化的话,我们可以看那个变量先 变化,然后更新,本质上是一样的。

例题

代码:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define int long long
#define ull unsigned long long
#define N 1000100
#define M number
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T>  inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

int phi[N],sumphi[N],prime[N],tail;
bool not_prime[N];

inline void getphi(){
    not_prime[1]=1;phi[1]=1;sumphi[1]=1;
    for(int i=2;i<=N;i++){
        if(!not_prime[i]) prime[++tail]=i,phi[i]=i-1;
        for(int j=1;j<=tail&&prime[j]*i<=N;j++){
            not_prime[prime[j]*i]=1;
            if(i%prime[j]==0){
                phi[prime[j]*i]=prime[j]*phi[i];
                break;
            }
            else phi[prime[j]*i]=phi[i]*phi[prime[j]];
        }
    }
//    for(int i=1;i<=100;i++) printf("%d ",phi[i]);
    for(int i=1;i<=N;i++) sumphi[i]=sumphi[i-1]+phi[i];
}

signed main(){
    getphi();int n;
    while(scanf("%lld",&n)){
        if(n==0) break;
        int ans=0;
        for(int i=1;i<=n;){
            int j=n/(n/i);
            ans+=(sumphi[j]-sumphi[i-1])*(n/i)*(n/i);
            i=j+1;
        }
        ans-=(1+n)*n/2;
        printf("%lld\n",ans/2);
    }
    return 0;
}

例题2

\(\sum\limits_{i=1}^n\sum\limits_{j=1}^m[gcd(i,j)=1]\)

不难发现:

\[\sum_{i=1}^n\sum_{j=1}^mgcd(i,j)=\sum_{i=1}^n\sum_{j=1}^m\epsilon(gcd(i,j))=\sum_{i=1}^n\sum_{j=1}^m\sum\limits_{d|gcd(i,j)}\mu(d)=\sum\limits_{d=1}^{\min(n,m)}\mu(d)\left\lfloor\dfrac{n}{d}\right\rfloor\left\lfloor\dfrac{m}{d}\right\rfloor \]

可以解决这个题。

例题3

对于 \(1\le x\le n,1\le y\le n\) ,求解 \(\sum\limits_{i=1}^n\sum\limits_{j=1}^m[gcd(i,j)\in Prime]\)

我们有一个非常奇怪的条件,我们考虑增加枚举量,但不是用卷积的形式。

\(\sum\limits_{i=1}^n\sum\limits_{j=1}^m[gcd(i,j)\in Prime]=\sum\limits_{i=1}^n\sum\limits_{j=1}^m\sum\limits_{p\in Prime}[gcd(i,j)=g]=\sum\limits_{p\in Prime}\sum\limits_{i=1}^{\left\lfloor\tfrac{n}{p}\right\rfloor}\sum\limits_{j=1}^{\left\lfloor\tfrac{m}{p}\right\rfloor}[gcd(i,j)=1]\)

然后这就转化成了类似于例题 \(2\)

上式变成了:

\[\sum\limits_{p\in Prime}\sum\limits_{d=1}^{\min(\tfrac{m}{d},\tfrac{n}{d})}\mu(d)\left\lfloor\dfrac{n}{pd}\right\rfloor\left\lfloor\dfrac{m}{pd}\right\rfloor \]

但是我们发现,我们还是有两重循环。

技巧 \(4\) :换元法:对于两个变量乘起来的换掉。

\(x=pd\),则上式变成:

\[\sum\limits_{x=1}^{min(n,m)}\sum\limits_{p\in Prime,p|x}\mu(\dfrac{x}{p})\times\left\lfloor\dfrac{n}{x}\right\rfloor\left\lfloor\dfrac{m}{x}\right\rfloor=\sum\limits_{x=1}^{min(n,m)}\left\lfloor\dfrac{n}{x}\right\rfloor\left\lfloor\dfrac{m}{x}\right\rfloor\times\sum\limits_{p\in Prime,p|x}\mu(\dfrac{x}{p}) \]

而后面那个东西,实际上可以利用积性函数预处理出来。这个题就做完了。

关于后面这个式子预处理方法,我们分类有:

\(p_1\)\(x\) 的最小素数,如果 \(p_1\)\(x\) 中的出现次数出现了两次及以上,那么有: \(f(x)=\mu(\frac{x}{p_1})\) ,否则有:\(f(x)=\mu(\frac{x}{p_1})-f(\frac{x}{p_1})\)

不过其实也可以枚举素数 \(p\) 的倍数来做,但是这样复杂度不是很对,网上有人说那是埃筛的复杂度,似乎近似于 \(O(n\ln n)\) 。这个复杂度是接近 \(O(n)\) 的 。


\(\text{upd}:\)

实测两种方法都是正确的,且线性做法比非线性快 \(1\) 秒。

下面的代码中,备注释掉的是非线性做法。

链接

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<sstream>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<deque>
#include<cstdlib>
#include<ctime>
#define dd double
#define ld long double
#define ll long long
#define ull unsigned long long
#define N 10000101
#define M 700000
using namespace std;

inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}

inline int Min(int a,int b){
	return a>b?b:a;
}

int p[M],tail,mu[N],g[N],t,minfac[N];
bool vis[N];
ll ans=0;

int main(){
	mu[1]=1;minfac[1]=1;
	for(int i=2;i<=N-100;i++){
		if(!vis[i]) p[++tail]=i,mu[i]=-1,minfac[i]=i;
		for(int j=1;j<=tail&&i*p[j]<=N-100;j++){
			int v=i*p[j];
			vis[v]=1;
			minfac[i*p[j]]=p[j];
			if(i%p[j]!=0) mu[v]=mu[i]*mu[p[j]];
			else mu[v]=0;
			if(i%p[j]==0) break;
		}
	}
	// for(int i=1;i<=tail;i++)
	// 	for(int j=1;j*p[i]<=N-100;j++)
	// 		g[j*p[i]]+=mu[j];
	for(int i=2;i<=N-100;i++){
		g[i]+=mu[i/minfac[i]];
		if(i%(minfac[i]*minfac[i])!=0) g[i]=g[i]-g[i/minfac[i]];
	}
	for(int i=1;i<=N-100;i++) g[i]+=g[i-1];
	t=read();
	while(t--){
		int n,m;ans=0;
		n=read();m=read();
		for(int l=1,r=0;l<=Min(n,m);l=r+1){
			r=Min(n/(n/l),m/(m/l));
			ans+=(ll)(g[r]-g[l-1])*(ll)(n/l)*(ll)(m/l);
		}
		printf("%lld\n",ans);
	}
	
	return 0;
}

之所以有后面这个式子,是因为我们考虑所有能对 \(f\) 造成贡献的数原先有 \(p_1\) ,现在没有了,所以符号变化了。

上面这种方法不失为一种做法,但是如果 \(n=m\) ,我们有一种优美的做法,从这里开始:

\[\sum\limits_{p\in Prime}\sum\limits_{i=1}^{\left\lfloor\tfrac{n}{p}\right\rfloor}\sum\limits_{j=1}^{\left\lfloor\tfrac{n}{p}\right\rfloor}[gcd(i,j)=1]\\ \]

我们先考虑:

\[\sum\limits_{i=1}^{\left\lfloor\tfrac{n}{p}\right\rfloor}\sum\limits_{j=1}^{i}[gcd(i,j)=1]\\ \]

不难发现,如果我们把 \(i\) 固定,后面这个东西实际上就是 \(\phi(i)\) ,所以如果我们要计算这个式子:

\[\sum\limits_{i=1}^{\left\lfloor\tfrac{n}{p}\right\rfloor}\sum\limits_{j=1}^{\left\lfloor\tfrac{n}{p}\right\rfloor}[gcd(i,j)=1]\\ \]

实际上就是上面这个式子乘上 \(2\) 再减去一些重复的部分,不难发现,只有 \(i=j\) 的情况被重复枚举了,但只有 \(i=j=1\) 的时候才会对答案造成贡献,所以我们直接乘 \(2\)\(1\) 就可以。

例题

代码:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define int long long
#define ull unsigned long long
#define N 14000100
#define M number
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T>  inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

int phi[N],sumphi[N],prime[N],tail;
bool not_prime[N];

inline void getphi(int n){
    not_prime[1]=1;phi[1]=1;sumphi[1]=1;
    for(int i=2;i<=n;i++){
        if(!not_prime[i]) prime[++tail]=i,phi[i]=i-1;
        for(int j=1;j<=tail&&prime[j]*i<=n;j++){
            not_prime[prime[j]*i]=1;
            if(i%prime[j]==0){
                phi[prime[j]*i]=prime[j]*phi[i];
                break;
            }
            else phi[prime[j]*i]=phi[i]*phi[prime[j]];
        }
    for(int i=1;i<=n;i++) sumphi[i]=sumphi[i-1]+phi[i];
}

int n,ans;

signed main(){
    scanf("%lld",&n);
    getphi(n);
    for(int i=1;i<=tail;i++){
        ans+=(sumphi[n/prime[i]]*2-1);
    }
    printf("%lld\n",ans);
    return 0;
}

一定要注意空间是否溢出,这个可能导致答案在本地是对的,但是评测机上不对。

posted @ 2021-05-28 09:58  hyl天梦  阅读(155)  评论(0编辑  收藏  举报