数论初步

· · 个人记录

参考资料

《算法竞赛进阶指南》

洛谷日报

OI Wiki

基本数论概念

整除、因数、倍数什么的就不用说了吧。

数论函数

指定义域为正整数的函数。下面讨论的『函数』如非特殊说明,都指数论函数。

积性函数

定义

若有 \forall \gcd(a,b)=1,f(ab)=f(a)f(b),则称 f 为积性函数。 若有 \forall a,b,f(ab)=f(a)f(b),则称 f 为完全积性函数。

性质

f(x),g(x) 都是积性函数,则以下函数也是积性函数:

f(x^p),f^p(x),f(x)g(x),\sum_{d|x}f(d)g(\frac xd)

前三个证明显然,第四个其实是这两个函数的狄利克雷卷积,后面会讲。

欧几里得算法

内容及其证明

求 gcd 的常用方法。其内容为:

\gcd(a,b)=\gcd(b,a\bmod b)

证明如下:

a<b 时,定理显然成立。

a\ge b 时,设 a=kb+r,其中 0\le r<b,即 r=a\bmod b。因为 \forall d\mid a,d\mid b 都有 d\mid kb,所以 d\mid (a-kb)d\mid r。于是我们得到结论:\forall d\mid a,d\mid b,\quad d\mid (a\bmod b)。因此,ba\bmod b 的公约数集相对于 ab 的公约数集是没有改变的,那么两个集合中最大的数自然不变。

证毕。

算法应用及其时间复杂度证明

开始前,先来证明一个结论:当 a\ge b 时,a\bmod b<\frac a2

注:证明过程中的 x 为任意不小于 1 的正实数。

$\therefore \lfloor\frac{a}{b}\rfloor>\frac{a}{2b} \therefore \lfloor\frac{a}{b}\rfloor b>\frac{a}{2} \therefore a-\lfloor\frac{a}{b}\rfloor b<\frac{a}{2} \therefore a\bmod b<\frac{a}{2}

根据欧几里得算法,代码很容易得出:

int gcd(int a,int b){
    return b?gcd(b,a%b):a;
}

其时间复杂度为 \mathcal O(\log a+\log b)。证明如下:

在求 \gcd(a,b) 时,会出现下面两种情况:

由于前者发生后一定会发生后者,所以前者的发生次数一定不多于后者,所以总的时间复杂度仍为 \mathcal O(\log a+\log b)

欧拉函数

内容

欧拉函数为 \varphi(n),表示小于等于 n 的与 n 互质的数的个数。即:

\varphi(n)=\sum_{i=1}^n[n\perp i]

性质

性质一

若在算数基本定理中,n=\prod_{i=1}^m p_i^{c_i},则:

\varphi(n)=n\prod_{i=1}^m(\frac {p_i-1}{p_i})

证明:容斥原理。设每个 n 的质因子 p_i[1,n] 中的倍数集合为 A_i。则有:

\varphi(n)=n-|\bigcup_{i=1}^m A_i|

又因为我们知道 |A_i|=\lfloor \frac n{p_i}\rfloor,于是就可以用容斥的公式:

\varphi(n)=n-\sum_{k=1}^m(-1)^{k-1}\sum_{1\le i_1<i_2<\cdots<i_k\le m}|\bigcap_{j=1}^kA_{i_j}|

我们还知道,|\bigcap_{i=1}^kA_{i}|=\lfloor\frac{n}{\prod_{i=1}^kp_i}\rfloor。于是:

\begin{aligned} \varphi(n)&=n-\sum_{k=1}^m(-1)^{k-1}\sum_{1\le i_1<i_2<\cdots<i_k\le m}\lfloor\frac n{\prod_{j=1}^kp_{i_j}}\rfloor\\ &=n(1-\sum_{k=1}^m(-1)^{k-1}\sum_{1\le i_1<i_2<\cdots<i_k\le m}\prod_{j=1}^k\frac 1{p_{i_j}})\\ &=n(1-\sum_{k=1}^{m-1}(-1)^{k-1}\sum_{2\le i_1<i_2<\cdots<i_k\le m}\prod_{j=1}^k\frac 1{p_{i_j}}-\frac{1}{p_1}+\sum_{k=1}^m(-1)^{k-1}\sum_{1=i_1<i_2<\cdots<i_k\le m}\prod_{j=1}^k\frac 1{p_{i_j}})\\ &=n(1-\frac{1}{p_1})(1-\sum_{k=1}^{m-1}(-1)^{k-1}\sum_{2\le i_1<i_2<\cdots<i_k\le m}\prod_{j=1}^k\frac 1{p_{i_j}})\\ &=\cdots\\ &=n\prod_{i=1}^m(1-\frac 1{p_i}) \end{aligned}

性质二

欧拉函数是积性函数。

证明:(其中 \gcd(a,b)=1,在唯一分解定理中,a=\prod_{i=1}^np_i^{c_i}b=\prod_{i=1}^mq_i^{k_i})。

\varphi(a)\varphi(b)=a\prod_{i=1}^n(1-\frac 1{p_i})\cdot b\prod_{i=1}^m(1-\frac 1{q_i})=\varphi(ab)

https://www.zhihu.com/question/410737433

性质三

\forall n>1,\sum_{i=1}^ni\cdot[n\perp i]=\frac{n\varphi(n)}2

证明(运用了莫比乌斯反演,可以先把整篇文章看完再回过头来看):

\sum_{i=1}^ni\cdot[n\perp i] &=\sum_{i=1}^ni\cdot\varepsilon(\gcd(n,i))\\ &=\sum_{i=1}^ni\sum_{d\mid n\land d\mid i}\mu(d)\\ &=\sum_{d\mid n}\mu(d)\sum_{i=1}^ni[d\mid i]\\ &=\sum_{d\mid n}\mu(d)d\sum_{i=1}^{\frac nd}i\\ &=\frac 12\sum_{d\mid n}\mu(d)d(\frac nd+1)\frac nd\\ &=\frac n2\sum_{d\mid n}\mu(d)(\frac nd+1)\\ &=\frac n2((\mathbf 1*\mu)(n)+\sum_{d\mid n}\mu(d)\mathbf{id}(\frac nd))\\ &=\frac{n(\varepsilon(n)+\varphi(n))}2 \end{aligned}

upd: 我太 naive 了。现在告诉大家一种更简单的证明方法。

这个式子其实在算不超过 n 的与 n 互质的数的和。我们知道 \gcd(a,b)=\gcd(b,a-b),所以对于每个 i\perp n,都有 (n-i)\perp n,它们的和等于 n,所以事实上就是 \frac{\varphi(n)}{2}n 相加。

埃氏筛与欧拉筛

埃氏筛

这种筛法虽然时间复杂度逊于欧拉筛,但是对于题目的一些特殊要求能够更好地应付。例如 筛一段区间内的质数 就只能用埃氏筛。

埃氏筛的代码如下:

for(ll i=2;i<=N;i++){
    if(!vis[i]){
        p.push_back(i);
        if(i*i<=N){
            for(ll j=i*i;j<=N;j+=i)vis[j]=1;
        }
    }
}

可以看出,埃氏筛是对于每个质数,把能整除它的所有和数全部标记一遍。但是一个小于 i^2 的合数必定有比 i 更小的质因子,所以它肯定已经被筛过,没有必要再筛。

如果想要筛一段区间 [L,R] 内的质数,只需要用 [1,\sqrt R] 内的质数去筛就可以了。

设需要筛出的质数范围为 [1,N],则其时间复杂度为 \mathcal O(N\log\log N),证明需要用微积分,然而我不会,会微积分的可以取 OI Wiki 看证明。 现在会了

欧拉筛

现在我们想筛出 2N 的所有质数。

欧拉筛的核心思想是从大到小累计质数,以达到每个合数只筛一次的目的。算法用一个数组来记录每个数的最小质因子。设这个数组为 v,我们可以用以下方式维护它:

注意第三点,因为质因子是从大到小累积的,所以 p 一定是 pi 的最小质因子。

该算法从 2N 的每个数都只会被遍历一次,筛一次,所以时间复杂度为 \mathcal O(N)

P3383 线性筛质数模板的核心代码:

void init(int N){
    for(int i=2;i<=N;i++){
        if(!v[i]){v[i]=i;p.push_back(i);}
        for(int pr:p){
            if(pr>v[i]||pr>N/i)break;
            v[pr*i]=pr;
        }
    }
}

线性筛欧拉函数

线性筛过程中,假设我们正在用一个数 n 和一个质数 ppn,且已知 \varphi(n),需要求出 \varphi(pn)。设在唯一分解定理中,n=\prod_{i=1}^mq_i^{c_i}

然后就能求出 [2,N] 中的每一个整数的欧拉函数值了。只需在上面的程序中稍加修改:

inline void init(int n){
    for(int i=2;i<=n;i++){
        if(!v[i]){v[i]=i;phi[i]=i-1;p.push_back(i);}
        for(int pr:p){
            if(pr>v[i]||pr>n/i)break;
            v[pr*i]=pr;
            phi[pr*i]=phi[i]*(pr==v[i]?pr:phi[pr]);
        }
    }
}

线性筛任意积性函数

已知 f 是一个积性函数。设在唯一分解定理中,n=\prod_{i=1}^mp_i^{c_i}。则有:

\mathbf f(n)=\prod_{i=1}^m\mathbf f(p^{c_i})

因此,只要在欧拉筛时,统计每个数最小质因数 p 及其次数 c,就可以递推计算积性函数:

\mathbf f(n)=\mathbf f(p^c)\mathbf f(\frac n{p^c})

以后,在讨论积性函数时,只讨论其在 p^k 的取值然后相乘的方法,是比较常见的。

例题

P5481 [BJOI2015] 糖果

你可能会问这不是一道组合计数问题吗,为啥放到数论这里。

因为这道题的难点不在于计数,而在于任意模数。

计数式子插板法随便搞搞就得出来了,是

\binom{m+k-1}{k-1}^{\underline n}=(\frac{(m+k-1)^{\underline m}}{m!})^{\underline n}

所以任意模数怎么办呢?如果直接 exgcd 算很可能出现逆元不存在的情况,而且还不一定是模数的倍数,所以答案不一定是 0

然后我就忍不住看了题解,发现只要把 m! 分解质因数,然后把上面的式子除掉这些质因数就行了。

这里有一个分解阶乘质因数的技巧:枚举所有小于等于 m 的质数 p,然后计算 pm! 中出现的次数。这个次数就等于能整除 p 的数的数量加上能整除 p^2 的数的数量加上能整除 p^3 的数的数量,等等。这样一来,如果一个数含有 kp,其贡献就为 k,所以这个算法是正确的。故 pm! 中出现的的次数为:

\sum_{p^k\le m}\lfloor\frac m{p^k}\rfloor

代码如下:

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=1e5+5;
int pr[N],c[N],d[N],tot;int P,v[N];
inline void init(int m){
    const int n=N-5;
    for(int i=2;i<=n;i++){
        if(!v[i])pr[++tot]=v[i]=i;
        for(int j=1;j<=tot;j++){
            if(pr[j]>v[i]||pr[j]>n/i)break;
            v[pr[j]*i]=pr[j];
        }
    }
    for(int i=1;i<=tot;i++){
        if(m<i)break;
        ll x=pr[i];
        while(m>=x){
            c[i]+=m/x;
            x*=pr[i];
        }
    }
}
inline ll dow(ll a,int b){
    ll ret=1;
    while(b--){
        ret=(ret*a)%P;
        a--;
    }
    return ret;
}
int n,m,K;
int main(){
    scanf("%d%d%d%d",&n,&m,&K,&P);
    init(m);ll ans=1;
    for(int i=K;i<K+m;i++)d[i-K]=i;
    for(int i=1,p;i<=tot;i++){
        p=pr[i];if(!c[i])break;
        for(int j=(K%p?(K/p+1)*p:K);j<K+m;j+=p){
            while(c[i]&&d[j-K]%p==0)d[j-K]/=p,c[i]--;
        }
    }
    for(int i=K;i<K+m;i++)ans=(ans*d[i-K])%P;
    printf("%lld\n",dow(ans,n));
    return 0;
}

P2158 仪仗队

一个人 (x,y) 能被看到,当且仅当 x+y=1\gcd(x,y)=1(这里下标从 0 开始)。然后因为正方形是沿对称轴对称的,所以我们可以先算出答案的不严格一半,即 x\ge y 的情况。

于是,问题转化为:求 \text{ans}=\sum_{x=1}^N\sum_{y=1}^x[\gcd(x,y)=1]=\sum_{x=1}^n\varphi(x)

线性筛一下就好了,答案为 2(\text{ans}-1)+3,其中 -1 是减去 (1,1) 的情况(因为这个点在对称轴上)+3 是加上 (0,1),(1,0),(1,1) 的情况。

细节:注意 \varphi(1)=1,需要提前赋值。注意输入要减一,而且要特判输入等于一的情况。

该算法的时间复杂度为 \mathcal O(n)

我的代码:

#include<cstdio>
#include<vector>
using namespace std;
const int N=4e4+5;
int v[N],phi[N];
long long ans;
vector<int>p;
inline void init(int n){
    ans=phi[1]=1;
    for(int i=2;i<=n;i++){
        if(!v[i]){v[i]=i;phi[i]=i-1;p.push_back(i);}
        for(int pr:p){
            if(pr>v[i]||pr>n/i)break;
            v[pr*i]=pr;
            phi[pr*i]=phi[i]*(pr==v[i]?pr:phi[pr]);
        }
        ans+=phi[i];
    }
}
int n;
int main(){
    scanf("%d",&n);
    init(n-1);
    printf("%lld",(n-1)?((ans-1)<<1)+3:0ll);
    return 0;
}

同余

a\bmod m=b\bmod m,则称 a\equiv b\pmod m

同余类和剩余系

定义

对于 a\in[0,m-1],将集合 \{x\in\mathbb{N}\mid x\equiv a\pmod m\}=\{a+km\mid k\in\mathbb{N}\} 称为模 m 的一个同余类,简记为 \overline a

$1$ 到 $m$ 中与 $m$ 互质的数所代表的剩余系有 $\varphi(m)$ 个,它们构成 $m$ 的简化剩余系。 ### 性质 简化剩余系关于模 $m$ 乘法封闭。 ### 证明 设 $a,b$ 是 $m$ 简化剩余系中的两个数,$a=k_1m+x,b=k_2m+y\ (1\le x,y<m)$。 则 $ab=(k_1m+x)(k_2m+y)=k_1k_2m^2+k_2mx+k_1my+xy$,所以 $ab\equiv xy\pmod m

根据定义,\gcd(x,m)=\gcd(y,m)=1,所以 \gcd(xy,m)=1

根据欧几里得算法公式,\gcd(xy,m)=\gcd(m,xy\bmod m)=1,则简化剩余系关于模 m 乘法封闭。

证毕。

欧拉定理和费马小定理

内容

欧拉定理:若正整数 a,n 互质,则 a^{\varphi(n)}\equiv 1\pmod n

费马小定理:若 p 是质数,则对于任意整数 a,有 a^p\equiv a\pmod n

证明

n 的简化剩余系为 \{\overline{a_1},\overline{a_2},\cdots,\overline{a_{\varphi(n)}}\}。根据定义,1\le a_i<n

\because\gcd(a,n)=1,\gcd(a_i,n)=1 \therefore\gcd(aa_i,n)=1$ 即 $\gcd(n,aa_i\bmod n)=1 设 $\overline{aa_i\bmod n}$ 和 $\overline{aa_j\bmod n}$ 是相同的同余类,则 $aa_i\equiv aa_j\pmod n$,即 $a(a_i-a_j)\equiv 0\pmod n \therefore n\mid a(a_i-a_j)$ 即 $n\mid (a_i-a_j) \therefore a_i\equiv a_j\pmod n \therefore a_i=a_j

于是我们得到一个成立的命题:『如果 \overline{aa_i\bmod n}\overline{aa_j\bmod n} 是相同的同余类,那么a_i=a_j』。因此它的逆否命题『如果 a_i\ne a_j,那么 \overline{aa_i\bmod n}\overline{aa_j\bmod n} 不是相同的同余类』也成立。

\therefore\{\overline{aa_1\bmod n},\overline{aa_2\bmod n},\cdots,\overline{aa_{\varphi(n)}\bmod n}\}=\{\overline{a_1},\overline{a_2},\cdots,\overline{a_{\varphi(n)}}\}

于是:

a^{\varphi(n)}\prod_{i=1}^{\varphi(n)}a_i\equiv\prod_{i=1}^{\varphi(n)}aa_i\equiv\prod_{i=1}^{\varphi(n)}a_i\pmod n \therefore a^{\varphi(n)}\equiv 1\pmod n

至于费马小定理,其实就是欧拉定理的一个特例(除了 p\mid n 的情况,不过这种情况下费马小定理显然成立)。

欧拉定理的推论

内容

\forall\gcd(a,n)=1,\quad a^b\equiv a^{b\bmod\varphi(n)}\pmod n

证明

\begin{aligned} a^b&\equiv a^{\varphi(n)\lfloor\frac b{\varphi(n)}\rfloor+b\bmod\varphi(n)}\\ &\equiv 1^{\lfloor\frac b{\varphi(n)}\rfloor}\times a^{b\bmod\varphi(n)}\\ &\equiv a^{b\bmod\varphi(n)}\pmod n\\ \end{aligned}

扩展欧拉定理

\forall b\ge\varphi(n),\quad a^b\equiv a^{b\bmod\varphi(n)+\varphi(n)}\pmod n

证明

例题

P5091 【模板】扩展欧拉定理

套公式即可。代码是很久以前写的,所以码风可能和现在不一样。

#include<cctype>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
inline ll read(){
    char c=getchar();ll x=0;bool f=0;
    for(;!isdigit(c);c=getchar())f^=!(c^45);
    for(;isdigit(c);c=getchar())x=(x<<1)+(x<<3)+(c^48);
    return f?-x:x;
}
inline ll getB(ll mod){
    char c=getchar();ll b=0;bool f=0;
    for(;!isdigit(c);c=getchar());
    for(;isdigit(c);c=getchar()){
        b=(b<<1)+(b<<3)+(c^48);
        if(b>mod)b%=mod,f=1;
    }
    return f?b+mod:b;
}
inline ll phi(ll x){
    ll res=x;
    for(register ll i=2;i*i<=x;i++){
        if(x%i==0){
            res=res*(i-1)/i;
            while(x%i==0)x/=i;
        }
    }
    if(x>1)res=res*(x-1)/x;
    return res;
}
inline ll quickPower(ll a,ll b,ll n){
    ll res=1;
    while(b){
        if(b&1){
            res=(res*a)%n;
        }
        a=(a*a)%n;
        b>>=1;
    }
    return res;
}
ll a,m,phim,b;
int main(){
    a=read();m=read();
    phim=phi(m);
    b=getB(phim);
    printf("%lld",quickPower(a,b,m));
    return 0;
}

数论分块

过程

对于形如 \sum_{i=1}^nf(i)g(\lfloor\frac ni\rfloor) 的式子,由于 \left\lfloor\dfrac ni\right\rfloor 的取值至多只有 2\sqrt n 种,因此可以把和式分块计算,而 f(i) 使用前缀和处理。

结论:值 \lfloor\frac ni\rfloor 所在块的右端点为

\left\lfloor\dfrac{n}{\left\lfloor\frac ni\right\rfloor}\right\rfloor

我们也可以类似地搞一个二维(或多维)数论分块,用于求解类似 \sum_{i=1}^nf(i)g(\lfloor\frac ni\rfloor)h(\lfloor\frac mi\rfloor) 的式子。这时我们应该取右端点为所有维度右端点的最小值。例如二维数论分块应为 r=min(n/(n/i),m/(m/i))

显然,二维数论分块的时间复杂度仍然是 \mathcal O(\sqrt n) 的(假设 nm 同级)。

证明

k=\lfloor\dfrac ni\rfloor

\begin{aligned} \because&k\le \frac ni\\ \therefore&\lfloor\frac nk\rfloor\ge \lfloor\frac n{\frac ni}\rfloor=\lfloor i\rfloor=i\\ \therefore&i\le\left\lfloor\dfrac{n}{\left\lfloor\frac ni\right\rfloor}\right\rfloor \end{aligned}

例题

UVA11526

题意

\sum_{i=1}^n\lfloor\dfrac ni\rfloorT 组数据。

分析

模板题。按照上面的流程即可。注意边界情况和运算顺序。

时间复杂度为 \mathcal O(T\sqrt n)

代码

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
ll H(ll n){
    ll ret=0;
    for(ll l=1,r=0;l<=n;l=r+1){
        r=n/(n/l);ret+=(r-l+1)*(n/l);
    }
    return ret;
}
int main(){
    ll T,n;
    scanf("%lld",&T);
    while(T--){
        scanf("%lld",&n);
        printf("%lld\n",H(n));
    }
    return 0;
}

狄利克雷卷积

从现在开始,数论函数一律使用粗黑体(如 \mathbf f,\mathbf g)或希腊字母(如 \varepsilon)。

定义两个数论函数的加法为 (\mathbf{f+g})(n)=\mathbf f(n)+\mathbf g(n),点乘为 (\mathbf{f\cdot g})(n)=\mathbf{f}(n)\mathbf{g}(n)。下面将省略点乘符号。

\mathbf f 是完全积性函数时,\mathbf{(fg)*(fh)=f(g*h)}

定义

运算

(\mathbf{f*g})(n)=\sum_{d\mid n}\mathbf f(d)\mathbf g(\frac nd)

为狄利克雷卷积。也可以写成:

(\mathbf{f*g})(n)=\sum_{ij=n}\mathbf f(i)\mathbf g(j)

性质

\mathbf{f*g}=\mathbf{g*f}\\ \mathbf{f*(g*h)}=\mathbf{(f*g)*h}\\ \mathbf{(f+g)*h}=\mathbf{f*h+g*h}\\ x\mathbf{f*g}=x(\mathbf{f*g})

有单位元 \varepsilon(n)=[n=1],即 \mathbf f*\varepsilon=\mathbf f

\mathbf{f,g} 都是积性函数时,\mathbf{f*g} 也是积性函数。

一个积性函数的逆也是积性函数。

对于任意 \mathbf f(1)\ne 0 的函数 \mathbf f,存在逆元 \mathbf g 使得 \mathbf{f*g}=\varepsilon

\mathbf g\mathbf f 的逆元,只需要满足以下式子即可:

\mathbf g(n)=\frac{1}{\mathbf f(1)}([n=1]-\sum_{d\mid n,d\ne 1}\mathbf f(d)\mathbf g(\frac nd))

证明

挑一些不那么明显的。

结合律:

\begin{aligned} \mathbf{f*(g*h)}(n)&=\sum_{ij=n}\mathbf f(i)\sum_{kl=j}\mathbf g(k)\mathbf h(l)\\ &=\sum_{ikl=n}\mathbf f(i)\mathbf g(k)\mathbf h(l)\\ \end{aligned}

这就与三个函数的顺序无关了。

\mathbf f 的逆元 \mathbf g

\mathbf{f*g}=\varepsilon\\ \sum_{d\mid n}\mathbf f(d)\mathbf g(\frac nd)=[n=1]\\ \mathbf g(n)\mathbf f(1)+\sum_{d\mid n,d\ne 1}\mathbf f(d)\mathbf g(\frac nd)=[n=1]\\ \mathbf g(n)=\frac{1}{\mathbf f(1)}([n=1]-\sum_{d\mid n,d\ne 1}\mathbf f(d)\mathbf g(\frac nd))

其他先鸽了。

一些常见的数论函数及其性质

定义

\mathbf 1(x)=1\\ \mathbf{id}(x)=x,\mathbf{id}^k(x)=x^k\\ \varphi(x)=\sum_{i=1}^x[x\perp i]\\ \sigma_0(x)=\mathbf d(x)=\sum_{d\mid x} 1\\ \sigma(x)=\sum_{d\mid x}d,\sigma_k(x)=\sum_{d\mid x}d^k

定义 \mu\mathbf 1 的逆元。即 \mu*\mathbf 1=\varepsilon

性质

定义中提到的所有函数均为积性函数。其中前两个是完全积性函数。

\mathbf d=\mathbf{1*1}\\ \sigma=\mathbf{1*id}\\ \mathbf{id}=\mathbf 1*\varphi\\ \sigma=\mathbf{1*1}*\varphi=\mathbf d*\varphi\\ \varphi=\mathbf{id}*\mu\\ \mathbf 1=\mathbf d*\mu\\ \mathbf{id}=\sigma*\mu

其实只要记住前三个,后面的都可以自己推出来。

\mu(p^k)= \begin{cases} 1&k=0\\ -1&k=1\\ 0&k>1 \end{cases}

这个式子自己代入定义就很容易得到了。

\varphi(xy)=\frac{\varphi(x)\varphi(y)\gcd(x,y)}{\varphi(\gcd(x,y))}\\ \mathbf{d}(xy)=\sum_{i\mid x}\sum_{j\mid y}[i\perp j]

证明

前两个都很显然。这里证明第三个。设 p 为任意质数。

\begin{aligned} (\mathbf 1*\varphi)(p^k)&=\sum_{d\mid p^k}\varphi(d)\\ &=1+\sum_{i=1}^k\varphi(p^i)\\ &=1+\sum_{i=1}^k p^i(1-\frac 1p)\\ &=1+\sum_{i=1}^k p^i-p^{i-1}\\ &=p^k \end{aligned}

由于 \mathbf 1*\varphi 是积性函数,所以有:

\begin{aligned} (\mathbf 1*\varphi)(n)&=\prod(\mathbf 1*\varphi)(p^k)\\ &=\prod p^k\\ &=n\\ &=\mathbf{id}(n) \end{aligned}

倒数第二个可以用前面提到的欧拉函数的性质证明。以后做题推式子要注意不要忘记前面提到的公式。

最后一个可以通过分类讨论质数在 x,y 中的分布情况证明。

莫比乌斯反演

定理

\mathbf g=\mathbf{f*1},则有 \mathbf f=\mathbf{g}*\mu

当然,一般的题目肯定不会直接写出卷积的形式。

所以我们这么写:

\mathbf g(n)=\sum_{d\mid n}\mathbf f(d)\iff \mathbf f(n)=\sum_{d\mid n}\mu(d)\mathbf g(\frac nd)

魔力筛

虽然在网上没看见过这种叫法,倒是搜到了魔力筛让天价药物降价,但机房都这么叫,我也暂且这么叫吧。

这是一个可以在 \mathcal O(n\log\log n) 的时间复杂度求出 任意数论函数\mu 的狄利克雷卷积的算法。当然,必须保证这个数论函数能被提前计算出来。

\mathbf g_{i,n}=\sum_{d\mid n}\mathbf f(d)\mu(\dfrac nd),其中 d 只含前 i 种质因子。则有:

\mathbf g_{i,n}= \begin{cases} \mathbf g_{i-1,n}&p_i\nmid n\\ \mathbf g_{i-1,n}-\mathbf g_{i-1,\frac n{p_i}}&p_i\mid n \end{cases}

需要滚动数组优化。

虽然可以被解释为高维差分,但我不会。

其实这个 DP 方程也比较好理解。第一种情况显然正确。我们来看看第二种:

\sum_{d\mid n}\mathbf f(d)\mu(\dfrac nd)=\sum_{x\mid n}\mathbf f(x)\mu(\dfrac nx)-\sum_{y\mid\frac n{p_i}}\mathbf f(y)\mu(\dfrac n{p_iy})

其中 d 只包含前 i 种质因子,x,y 只包含前 i-1 种质因子。我们知道,每多一个质因子,\mu 的取值就会乘以 -1,因此上式的意义是:强制不选 p_i,然后乘以 -1 累加在答案上。如果含有平方因子,则值为 0,不影响答案。

复杂度和埃氏筛一样。

参考代码:

inline void calc(int n){
    for(int i=1;i<=n;i++)g[i]=f[i];
    for(int p:pr){
        for(int i=n/p;i>=1;i--){
            g[i*p]=g[i*p]-g[i];
        }
    }
}

应用

练习题

题目描述

给定 n,m\le 10^6,求

\sum_{i=1}^n\sum_{j=1}^m\gcd(i,j)

题目分析

我们知道 d\mid\gcd(a,b)\iff d\mid a\land d\mid b

有一个常见的套路:若要求 \mathbf f(\gcd(x,y)),可以先找到一个数论函数 \mathbf g=\mathbf{1*f},这样子就有:

\begin{aligned} \mathbf f(\gcd(x,y))&=\sum_{d\mid\gcd(x,y)}\mathbf g(d)\\ &=\sum_{d\mid x\land d\mid y}\mathbf g(d)\\ &=\mathbf g(d)\sum[d\mid x][d\mid y] \end{aligned}

于是解决这道题就变得很简单了。我们知道 \mathbf{id}=\mathbf 1*\varphi。所以好像并不需要莫比乌斯反演

\begin{aligned} \sum_{i=1}^n\sum_{j=1}^m\gcd(i,j)&=\sum_{i=1}^n\sum_{j=1}^m\mathbf{id}(\gcd(i,j))\\ &=\sum_{i=1}^n\sum_{j=1}^m\sum_{d\mid i\land d\mid j}\varphi(d)\\ &=\sum_{d=1}^{\min\{n,m\}}\varphi(d)\sum_{i=1}^n\sum_{j=1}^m[d\mid i][d\mid j]\\ &=\sum_{d=1}^{\min\{n,m\}}\varphi(d)\lfloor\frac nd\rfloor\lfloor\frac md\rfloor \end{aligned}

然后就可以用二维数论分块搞了。不过现在应该不会出这么套路的题了吧。

[国家集训队]Crash的数字表格 / JZPTAB

题目描述

给定 n,m,求

\sum_{i=1}^n\sum_{j=1}^m\operatorname{lcm}(i,j)

20101009 取模的结果。

数据范围:1\le n,m\le 10^7

题目分析

\mathbf{id}^{-1}=\mathbf{g*1},则 \mathbf g=\mathbf{id}^{-1}*\mu

\begin{aligned} \sum_{i=1}^n\sum_{j=1}^m\operatorname{lcm}(i,j)&=\sum_{i=1}^n\sum_{j=1}^mij\mathbf{id}^{-1}(\gcd(i,j))\\ &=\sum_{i=1}^n\sum_{j=1}^mij\sum_{d\mid i\land d\mid j}\mathbf g(d)\\ &=\sum_{d=1}^{\min\{n,m\}}\mathbf g(d)\sum_{i=1}^n\sum_{j=1}^m[d\mid i][d\mid j]ij\\ &=\frac 14\sum_{d=1}^{\min\{n,m\}}\mathbf g(d)d^2\lfloor\frac nd\rfloor(\lfloor\frac nd\rfloor+1)\lfloor\frac md\rfloor(\lfloor\frac md\rfloor+1) \end{aligned} 时间复杂度为 $\mathcal O(n)$。~~数论分块好像没有必要,如果不用数论分块甚至可能会快一点~~ #### 代码 ```cpp #include<cstdio> #include<vector> #include<algorithm> using namespace std; typedef long long ll; const int N=1e7+5,P=20101009; int v[N],t[N]; vector<int>pr; ll inv[N],g[N],s[N]; inline void init(int n){ inv[1]=1;g[1]=1;s[1]=1; for(int i=2;i<=n;i++){ inv[i]=(P-P/i)*inv[P%i]%P; if(!v[i]){ g[i]=(inv[i]+P-1)%P; pr.push_back(t[i]=v[i]=i); }else{ if(i==t[i])g[i]=(inv[i]-inv[i/v[i]]+P)%P; else g[i]=g[t[i]]*g[i/t[i]]%P; } for(int p:pr){ if(p>v[i]||p>n/i)break; v[i*p]=p;t[i*p]=(v[i]==p?t[i]*p:p); } s[i]=(s[i-1]+g[i]*i%P*i%P)%P; } } inline ll calc(int n,int m){ ll ret=0; for(int l=1,r;l<=n;l=r+1){ r=min({n,n/(n/l),m/(m/l)}); ret=(ret+(s[r]-s[l-1]+P)%P*(n/l)%P*(n/l+1)%P*(m/l)%P*(m/l+1)%P)%P; } return ret*inv[4]%P; } int main(){ int n,m;scanf("%d%d",&n,&m); if(n>m)swap(n,m);init(n); printf("%lld\n",calc(n,m)); return 0; } ``` ### [[SDOI2017]数字表格](https://www.luogu.com.cn/problem/P3704) #### 题目描述 给定 $n,m$,求 $$ \prod_{i=1}^n\prod_{j=1}^mF_{\gcd(i,j)} $$ 对 $10^9+7$ 取模的结果。其中 $F_i$ 表示斐波那契数列的第 $i$ 项。 有 $T$ 组数据。数据范围:$1\le T\leq 10^3$,$1\le n,m\le 10^6$。 #### 题目分析 我们定义 $\mathbf f(n)=\ln F_n$,$\mathbf{g*1}=\mathbf f$。则: $$ \begin{aligned} \prod_{i=1}^n\prod_{j=1}^mF_{\gcd(i,j)} &=\exp\sum_{i=1}^n\sum_{j=1}^m\mathbf f(\gcd(i,j))\\ &=\exp\sum_{i=1}^n\sum_{j=1}^m\sum_{d\mid i\land d\mid j}\mathbf g(\gcd(i,j))\\ &=\exp\sum_{d=1}^{\min\{n,m\}}\mathbf g(d)\sum_{i=1}^n\sum_{j=1}^m[d\mid i][d\mid j]\\ &=\exp\sum_{d=1}^{\min\{n,m\}}\mathbf g(d)\lfloor\frac nd\rfloor\lfloor\frac md\rfloor\\ &=\prod_{d=1}^{\min\{n,m\}}\exp(\sum_{ij=d}\mathbf f(i)\mu(j)\lfloor\frac nd\rfloor\lfloor\frac md\rfloor)\\ &=\prod_{d=1}^{\min\{n,m\}}(\prod_{i\mid d}\exp(\mathbf f(i)\mu(\frac di)))^{\lfloor\frac nd\rfloor\lfloor\frac md\rfloor}\\ &=\prod_{d=1}^{\min\{n,m\}}(\prod_{i\mid d}F_i^{\mu(\frac di)})^{\lfloor\frac nd\rfloor\lfloor\frac md\rfloor} \end{aligned} $$ 这个式子可以外层数论分块,内层暴力枚举计算。 单次询问的时间复杂度为 $\mathcal O(\sqrt n)$。注意对 $\mu$ 的处理。 另一种方法是使用类似魔力筛的方法计算内层,从而使预处理的时间复杂度降到 $\mathcal O(n\log\log n)$,请自行参阅题解。 #### 代码 ```cpp #include<cstdio> #include<vector> #include<algorithm> using namespace std; typedef long long ll; const int N=1e6+5,P=1e9+7; inline ll _pow(ll a,ll b){ ll ret=1;a%=P; while(b){if(b&1)ret=(ret*a)%P;a=a*a%P;b>>=1;} return ret; } int mu[N],v[N]; ll F[N],G[N]; vector<int>pr; inline void init(){ mu[1]=1;F[0]=0;F[1]=1;G[0]=G[1]=1; const int n=N-5; for(int i=2;i<=n;i++){ G[i]=1;F[i]=(F[i-1]+F[i-2])%P; if(!v[i])pr.push_back(v[i]=i),mu[i]=-1; for(int p:pr){ if(p>v[i]||p>n/i)break; v[i*p]=p; if(v[i]==p)mu[i*p]=0; else mu[i*p]=mu[i]*mu[p]; } } ll mF; for(int i=2;i<=n;i++){ mF=_pow(F[i],P-2); for(int j=i;j<=n;j+=i){ if(mu[j/i]==-1)G[j]=G[j]*mF%P; else if(mu[j/i]==1)G[j]=G[j]*F[i]%P; } } for(int i=2;i<=n;i++){ G[i]=G[i-1]*G[i]%P; } } int main(){ int T,n,m;scanf("%d",&T); ll ans;init(); while(T--){ scanf("%d%d",&n,&m); if(n>m)swap(n,m); ans=1; for(int l=1,r;l<=n;l=r+1){ r=min(n/(n/l),m/(m/l)); ans=ans*_pow(G[r]*_pow(G[l-1],P-2)%P,(ll)(n/l)*(m/l))%P; } printf("%lld\n",ans); } return 0; } ``` ### [YY 的 GCD](https://www.luogu.com.cn/problem/P2257) #### 题目大意 设 $P$ 为所有质数构成的集合。求 $$ \sum_{i=1}^n\sum_{j=1}^m[\gcd(i,j)\in P] $$ $T$ 组数据。数据范围:$T=10^4$,$n,m\le 10^7$。TL:4s。 #### 题目分析 设 $\mathbf f(n)=[n\in P]$,$\mathbf{g*1}=\mathbf f$。 然后就和上面一样了...... 直接写结果吧。 $$ \sum_{d=1}^{\min\{n,m\}}\mathbf g(d)\lfloor\frac nd\rfloor\lfloor\frac md\rfloor $$ 由于 $\mathbf f$ 不是积性函数,我们需要用魔力筛计算 $\mathbf g$。然后就可以数论分块搞了。 时间复杂度为 $\mathcal O(n\log\log n+T\sqrt n)$。 #### 代码 ```cpp #include<cstdio> #include<vector> #include<algorithm> using namespace std; typedef long long ll; const int N=1e7+5; int v[N],g[N]; vector<int>pr; inline void init(){ const int n=N-5; for(int i=2;i<=n;i++){ if(!v[i])pr.push_back(v[i]=i),g[i]=1; for(int p:pr){ if(p>v[i]||p>n/i)break; v[p*i]=p; } } for(int p:pr){ for(int i=n/p;i>=1;i--)g[i*p]-=g[i]; } for(int i=1;i<=n;i++)g[i]+=g[i-1]; } int main(){ init(); int n,m,T;scanf("%d",&T); ll ans; while(T--){ scanf("%d%d",&n,&m); if(n>m)swap(n,m);ans=0; for(int l=1,r;l<=n;l=r+1){ r=min({n,n/(n/l),m/(m/l)}); ans+=(ll)(g[r]-g[l-1])*(n/l)*(m/l); } printf("%lld\n",ans); } return 0; } ```