数论大复习

0x00 前言

本文包含大量数论内容,考试概率较低,慎入!!!

常考:快速幂素数筛法最大公约数与最小公倍数、逆元

前置知识:数论基础知识、符号表示等

0x01 快速幂 ★★★★☆

模板题:洛谷 P1226 【模板】快速幂

快速幂,分治算法的典型应用,可以以 \(O(\log p)\) 的优秀复杂度计算形如 \(a^b\bmod p\) 的问题。

对于朴素的算法,每次将 \(a^b\) 拆分成 \(a\times a^{b-1}\),进行 \(b\) 层的递归计算,时间开销较大,考虑减少递归层数。由于对于偶数 \(b\)\(a^b=a^{\frac{b}{2}}\times a^{\frac{b}{2}}\),只需计算 \(a^{\frac{b}{2}}\) 即可;而对于奇数 \(b\),考虑化归,由于 \(b-1\) 是奇数,我们先将 \(a^b\) 拆成 \(a\times a^{b-1}\),再按上述方法计算 \(a^{b-1}\) 即可。

综上,对于每一次递归中 \(a^b\) 的计算,若 \(b\) 为奇数,则拆为 \(a^{\frac{b-1}{2}}\times a^{\frac{b-1}{2}}\times a\);若 \(b\) 为偶数,则拆为 \(a^{\frac{b}{2}}\times a^{\frac{b}{2}}\)。对于边界条件,\(a^0=1(a\ne 0),a^1=a\),实现即可。

代码如下,时间复杂度 \(O(\log p)\)

int qpow(int a,int b,int p){
    if (!a && !b) return 1;
    if (b==1) return a%p;
    int x=qpow(a,b/2,p);
    return x*1ll*x%p*((b&1)?a:1)%p;
}

0x02 素数筛法 ★★★★☆

一、素数、合数

素数:若正整数 \(p\) 有且仅有 \(2\) 个正因子,则 \(p\) 是素数。
合数:若正整数 \(p\) 有多于 \(2\) 个正因子,则 \(p\) 是合数。

由定义可知,全部正整数可以分成 \(3\) 类:\(1\)、质数、合数。

二、素数的判断

根据定义,若 \(p\) 为素数,则 \(p\) 应只有 \(2\) 个正因子,即 \(1\) 与本身。所以,我们可以枚举从 \(2\)\(p-1\) 的所有数,看它们能否整除 \(p\) 即可,时间复杂度为 \(O(n)\)

考虑优化,如果 \(p=ab\ (a\le b)\),则有 \(p=ab\ge a^2\),即 \(a^2\) 始终小于等于 \(p\),我们只需将循环条件变为 \(a^2\le p\) 即可。

代码如下,时间复杂度 \(O(\sqrt{n})\)。这里顺便说一下为何不用等价的 \(a\le\sqrt{n}\) 作为条件。在 C++ 中,sqrt() 函数是一个二分查找的过程,时间复杂度为 \(O(\log n)\),如果直接写入循环条件,每循环一次后都要重新调用该函数,时间复杂度就会多带上一只 \(\log\),没有必要。

bool isPrime(int x){
    if (x<2) return 0;
    for (int i=2;i*i<=x;++i){ if (!(x%i)) return 0; }
    return 1;
}

三、埃拉托斯特尼筛法(埃筛)

模板题:洛谷 P3383 【模板】线性筛素数

Task 1

如果现在题目变成要你准确地找出 \(1\sim n\) 中所有的素数,使用上面的程序依次判断这 \(n\) 个数是否为素数,那么时间复杂度将为 \(O(n\sqrt{n})\),在一些题目中直接 T 飞了。怎么办呢?下面将介绍一种快速筛选出 \(1\sim n\) 中所有素数的算法:埃筛(埃拉托斯特尼筛法,Eratosthenes 筛法)。

有一件显而易见的事情:对于一个素数 \(p\),那么它的 \(x\) 倍应为合数,其中 \(x\in[2,+\infty)\cap\Z\)。所以,我们找到一个素数后,就可以将它所有大于本身的倍数都标记为合数。

具体地,设布尔数组 \(b_i\) 表示 \(i\) 是否为素数,初始化时 \(b_i=\begin{cases}0&,i<2\\ 1&,i\ge 2\end{cases}\)。往后,从 \(i=2\) 开始枚举,若当前 \(b_i=1\),说明 \(i\) 不是 \(2\)\(i-1\) 中任何一个数的倍数,从而 \(i\) 为素数,加入存放素数的数组 \(primes_k\)。然后遍历所有大于 \(i\)\(i\) 的倍数 \(j\),使 \(b_j=0\)。最后我们就得到了 \(primes_k\) 数组存储 \(1\sim n\) 的所有素数。

那么这个算法的时间复杂度是多少呢?由必应优先搜索算法可得其时间复杂度为 \(O(n\log\log n)\),具体怎么证详见 OI-WIKI。注意这里 \(\log\log n=\log(\log n)\),而不是 \(\log^2 n\)

Task 2:优化循环次数

虽然现在的时间复杂度已经显著高于用素数判断依次判断素数,但它还能优化。与素数判断相似,仍然考虑减少循环次数。由于当 \(i\ge\sqrt{n}\) 时,就算 \(i\) 是素数,那么 \(2i,3i,\dots\) 都已被其它素数筛过了(为什么?)而 \(i^2>(\sqrt{n})^2=n\),不在讨论范围之内。所以对于大于 \(\sqrt{n}\) 的数,直接判断是否加入 \(prime_k\) 数组即可,无需往后遍历。

为什么?:当 \(i\ge\sqrt{n}\) 时,对于 \(ai\ (2\le a\le\dfrac{n}{i}\le\sqrt{n}\)\(a\in\N_+)\),若 \(a\) 是素数,那么其必然在循环至 \(a\) 时被筛过;若 \(a\) 是合数,那么令 \(a=q\cdot b\),其中 \(q\) 为素数,\(b\in\N_+\)\(ai=q\cdot bi\) 必有 \(q\) 这个素因子,在循环至 \(q\) 时被筛。综上,命题得证。

Task 3:位级压缩

观察到该算法中频繁对布尔数组 \(b_i\) 进行操作,考虑 bitset。由于 bitset 对程序有常数的优化,使程序时间复杂度从 \(O(n\log\log n)\) 变成 \(O(\dfrac{n}{\omega}\log\log n)\),其中 \(\omega\) 为常数,一般为 \(32\)\(64\),压缩后其性能甚至能超过接下来讲的欧筛。(3.14s vs 3.23s)

bitset:详见 OI-WIKI

Task 4:最终代码

vector<int> solve(int n){
    vector<int> primes;
    bitset<N> b;
    b.set(),b[0]=b[1]=0;
    for (int i=2;i<=n;++i){
        if (b[i]){
            primes.push_back(i);
            if (i*i<n) for (int j=(i<<1);j<=n;j+=i) b[j]=0;
        }
    }
    return primes;
}

四、欧拉筛(欧筛、线性筛)

虽然埃筛的时间复杂度已经非常优秀,但能否做到真正的线性呢?继续优化。我们观察到,在埃筛中,有些数会被重复筛两次。如 \(6=2\times 3=3\times 2\),其既会被 \(2\) 筛,又会被 \(3\) 筛,如何避免呢?考虑制定规则,我们规定每一个合数只被其最小质因子筛

那么如何完成呢?依旧从 \(i=2\) 开始循环,若 \(b_i=1\),则 \(i\) 为素数,加入 \(primes_k\) 数组中。然后,无论 \(i\) 是不是素数,都往后筛,\(j\) 遍历 \(primes_k\) 中的元素,每次取出素数 \(j\)\(i\) 相乘,使 \(b_{ij}=0\)。此时,\(j\)\(ij\) 的最小质因子。持续遍历直至 \(j\mid i\),此时令 \(i=jl\),则后面的素数 \(j'\)\(i\) 相乘得到 \(ij'=jj'l\)。因为 \(j<j'\),而 \(j\)\(jl\) 的最小质因子,所以 \(j\) 也是 \(ij'\) 的最小质因子,所以 \(ij'\) 不应由 \(j'\) 来筛,而应有 \(j\cdot j'l\) 来筛。所以此时终止该层遍历即可。

代码如下,由于每个数最多被筛到一次,所以时间复杂度为 \(O(n)\)

vector<int> solve(int n){
    vector<int> primes;
    bitset<N> b;
    b.set(),b[0]=b[1]=0;
    for (int i=2;i<=n;++i){
        if (b[i]) primes.push_back(i);
        int l=primes.size();
        for (int j=0;j<l && i*primes[j]<=n;++j){ // 防止下标越界
            b[i*primes[j]]=0; // 筛
            if (!(i%primes[j])) break; // 核心优化!!!
        }
    }
    return primes;
}

0x03 费马小定理与欧拉定理 ★

费马小定理:对于 \(\forall a,p\in\Z\) 满足 \(p\) 为素数且 \(\gcd(a,p)=1\),则有 \(a^{p-1}\equiv 1\pmod p\)

欧拉定理:对于 \(\forall a,n\in\Z\) 满足 \(\gcd(a,n)=1\),有 \(a^{\varphi(n)}\equiv 1\pmod n\),其中 \(\varphi(n)\) 为欧拉函数,表示 \(1\sim n\) 中与 \(n\) 互质的数的个数,可通过枚举或下面公式算出。

计算公式:设 \(n=p_1^{\alpha_1}\cdot p_2^{\alpha_2}\cdot\dots\cdot p_k^{\alpha_k}\ (p_1,p_2,\dots,p_k\) 为质数\(,\alpha_1,\alpha_2,\dots,\alpha_k\in\N_+)\)(即质因数分解),则 \(\varphi(n)=n\times\prod_{i=1}^k\dfrac{p_i-1}{p_i}\)

证明:问数学老师吧OI WIKI

0x04 最大公约数/最小公倍数 ★★★★☆

一、定义

公约数:对于任意 \(a,b\in\N_+\),若 \(d\mid a\)\(d\mid b\)\(d\in\N_+\)),则 \(d\)\(a,b\) 的公约数。

最大公约数:顾名思义,\(a,b\) 的公约数中最大的那个数就叫 \(a,b\) 的最大公约数,用 \((a,b)\)\(\gcd(a,b)\) 表示。(由于 \(()\) 的含义过多,所以下文都用 \(\gcd()\) 表示最大公约数,但在数学学习中请按照数学老师要求书写)

公倍数:对于 \(a,b\in\N_+\),若 \(a\mid d\)\(b\mid d\)\(d\in\N_+\)),则 \(d\)\(a,b\) 的公倍数。

最小公倍数:\(a,b\) 的公倍数中最小的数即为 \(a,b\) 的最小公倍数,用 \([a,b]\)\(\operatorname{lcm}(a,b)\) 表示。(同上,下文采用第二种表示)

二、性质

性质:对于 \(a,b\in\N_+\),满足 \(ab=\gcd(a,b)\times\operatorname{lcm}(a,b)\)

证明:设 \(d=\gcd(a,b),a=a'd,b=b'd\),则 \(a',b'\) 互质。根据定义 \(\operatorname{lcm}(a,b)=da'b'\)(为什么?),从而 \(\gcd(a,b)\times\operatorname{lcm}(a,b)=d\times da'b'=a'd\cdot b'd=ab\)

为什么?:对于 \(a,b\) 的最小公倍数 \(ka\ (k\in\N_+)\),有 \(b\mid ka\),即 \(b'd\mid ka'd\),两边同约去 \(d\)\(b'\mid ka'\)。又由于 \(a',b'\) 互质,所以 \(b'\) 的所有质因子都来自于 \(k\),即 \(b'\mid k\),所以 \(k_{\min}=b'\)\(a,b\) 的最小公倍数即为 \(da'b'\)

三、辗转相除法

模板题:洛谷 B3634 最大公约数和最小公倍数

I. 算法流程及证明

辗转相除法,也称欧几里得算法,与我国古代的更相减损术类似。具体地,对于两正整数 \(a,b\),有 \(\gcd(a,b)=\gcd(b,a\bmod b)\)。考虑如何证明。

关于最小公约数,考虑先令 \(d=\gcd(a,b),a=a'd,b=b'd\)。有取余,再考虑使用带余除法,令 \(a=bk+r\ (q,r\in\Z,0\le q<b)\),则移项得 \(r=a-bq=a'd-kb'd\),从而 \(\gcd(b,a\bmod b)=\gcd(b,r)=\gcd(b'd,a'd-kb'd)=d\cdot\gcd(b',a'-kb')\)

考虑质因数分解,令 \(a'=\prod_{i=1}^m p_i^{\alpha_i},b'=\prod_{j=1}^n q_j^{\beta_j}\),其中 \(p_i,q_j\) 均为质数,\(\alpha_i,\beta_j\in\N_+\)。由于 \(a',b'\) 互质,所以对于 \(\forall i\in[1,m]\cap\Z,j\in[1,n]\cap\Z,p_i\ne q_j\)。从而对于 \(\forall j\in[1,n]\cap\Z\),有 \(q_j\nmid a',q_j\mid b'\),从而 \(q_j\nmid(a'-kb')\)。由于 \(b'\) 的任何质因数都不是 \(a'-kb'\) 的因数,所以 \(\gcd(b',a'-kb')=1\)。(为什么?)从而 \(\gcd(b,a\bmod b)=d\cdot 1=d=\gcd(a,b)\),证毕。

为什么?:对于 \(b',a'-kb'\) 的公约数 \(x\),对 \(x\) 进行质因数分解得到 \(x=\prod_{i=1}^k u_i^{\gamma_i}\),其中 \(u_i\) 为质数,\(\gamma_i\in\N_+\),则 \(u_i\mid b',u_i\mid(a'-kb')\),与 \(b'\) 的任何质因数都不是 \(a'-kb'\) 的因数矛盾。所以 \(b',a'-kb'\) 的任意公约数 \(x\) 都不能被分解成若干质数相乘,而由定义 \(x\in\N_+\),所以 \(x=1\)

最终代码如下。实现是注意 \(b=0\) 的边界判断。当该层 \(b=0\) 时,说明上一层 \(b_0\mid a_0\),即 \(\gcd(a_0,b_0)=b_0\),而上一层的 \(b_0\) 是这一层的 \(a\),所以直接返回 \(a\) 即可。对于最小公倍数,套用 \(ab=\gcd(a,b)\times\operatorname{lcm}(a,b)\) 即可。

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

II. 时间复杂度

对于时间复杂度,lcm() 函数只调用了参数相同的 gcd() 函数,所以二者时间复杂度相同。对于 gcd(a,b),若 \(a<b\),则执行一次后变成了 gcd(b,a),即时间复杂度最多在每一次都满足 \(a\ge b\) 的基础上乘常数 \(2\),对时间复杂度没有影响。因为 \(a=b\) 时下一层直接返回,递归到头开始回溯了,所以下面只讨论 \(a>b\) 时的情况。

\(a>b\) 时,若 \(a\ge 2b\),则下一层 gcd(b,a%b) 的规模最大为 gcd(b,b),至少缩小了一半。若 \(b<a<2b\),则下一层为 gcd(b,a-b),再下一层为 gcd(a-b,b%(a-b)),其中 \(a-b<a-\dfrac{a}{2}<\dfrac{a}{2}\)。我们希望 \(b\bmod(a-b)\le\dfrac{b}{2}\),这样我们就能证出两层后数据规模减小了一半。

那么能否证出呢?采用反证法。假设 \(b\bmod(a-b)>\dfrac{b}{2}\),又因为 \(b\bmod(a-b)<a-b\),所以 \(a-b>\dfrac{b}{2}\)。由于 \(a-b<2b-b=b\),所以 \(b\bmod(a-b)\) 计算过程中 \(b\) 至少被减掉了 \(1\)\(a-b\)(Tip),所以 \(b\bmod (a-b)<b-(a-b)<b-\dfrac{b}{2}=\dfrac{b}{2}\),与假设矛盾,所以假设不成立,所以原命题得证。

Tip:在取模运算中,对于 \(a\bmod b\),使用带余除法,令 \(a=bq+r\ (q,r\in\Z,0\le r<b)\),则 \(a\bmod b=r=a-bq\),即从 \(a\) 中减掉 \(q\)\(b\)

综上,经过 \(2\) 次递归后,数据规模不会超过原来的一半,所以欧几里得算法的时间复杂度因为 \(O(\log\max(a,b))\) 级别。

0x05 扩展欧几里得算法 ★

题目:对于 \(a,b\in\N_+\),求方程 \(ax+by=\gcd(a,b)\) 的一组整数解。

前置知识:欧几里得算法(辗转相除法)

而对于新方程,由欧几里得算法可得 \(ax+by=\gcd(a,b)=\gcd(b,a\bmod b)\),令其等于 \(bx'+(a\bmod b)y'=bx'+(a-b\times\big\lfloor\dfrac{a}{b}\big\rfloor)y'=ay'+b(x'-\big\lfloor\dfrac{a}{b}\big\rfloor\times y')\),两式对比可知 \(\begin{cases}x=y'\\ y=x'-\big\lfloor\dfrac{a}{b}\big\rfloor\times y'\end{cases}\) 是其一组解。所以在欧几里得算法的基础上,我们只需在每次递归返回时增加返回当前问题的 \(x,y\) 作为上一层递归的 \(x',y'\) 即可。

对于边界条件,普通欧几里得算法的边界为 \(b=0\),此处不变;当 \(b=0\) 时,方程变为 \(ax=a\ (a\ne 0)\),解得 \(x=1\),与 \(y\) 无关,令 \(y=0\) 即可。

代码如下,时间复杂度 \(O(\log(\max(a,b))\)

typedef pair<int,int> pii;
pii exgcd(int a,int b){
    if (b==0) return pii{1,0};
    pii p=exgcd(b,a%b);
    return pii{p.second,p.first-a/b*p.second};
}

0x06 裴蜀定理 ☆

模板题:洛谷 P4549 【模板】裴蜀定理

前置知识:扩展欧几里得算法

内容:对于方程 \(ax+by=c\ (a,b\in\N_+,c\in\Z)\),当且仅当 \(\gcd(a,b)\mid c\) 时,方程有整数解。

证明

Task 1:证条件

证明:仅当 \(\gcd(a,b)\mid c\) 时,方程可能有整数解。

由于 \(\gcd(a,b)\mid a\),所以 \(\gcd(a,b)\mid ax\),同理 \(\gcd(a,b)\mid by\),二者相加可得 \(\gcd(a,b)\mid (ax+by)\),即 \(\gcd(a,b)\mid c\)。所以当且仅当 \(\gcd(a,b)\mid c\) 时,原不定方程才有整数解。

Task 2:证存在

证明:当 \(\gcd(a,b)\mid c\) 时,方程有整数解。

使用扩展欧几里得算法,令 \(c=k\cdot\gcd(a,b)\),代回原方程得到 \(ax+by=k\cdot\gcd(a,b)\),即 \(a\cdot\dfrac{x}{k}+b\cdot\dfrac{y}{k}=\gcd(a,b)\),利用 Exgcd 可以求得其中一组解 \(\begin{cases}\dfrac{x}{k}=x'\\ \dfrac{y}{k}=y'\end{cases}(x',y'\in\Z)\),又因为 \(k\in\Z\),所以该方程的其中一组解 \(\begin{cases}x=kx'\\ y=ky'\end{cases}\) 为整数解,证毕。

拓展:\(n\) 元的裴蜀定理

内容:\(\gcd(a_1,a_2,\dots,a_n)\mid c\) 是方程 \(a_1x_1+a_2x_2+\dots+a_nx_n=c\) 有整数解的充分必要条件,其中 \(a_1,a_2,\dots,a_n\in\N_+,c\in\Z\)

Task 1:证必要

证明:当方程 \(a_1x_1+a_2x_2+\dots+a_nx_n=c\) 有整数解时,\(\gcd(a_1,a_2,\dots,a_n)\mid c\)

\(2\) 元的裴蜀定理该部分证明相似。由于 \(a_1,a_2,\dots,a_n\) 都是 \(\gcd(a_1,a_2,\dots,a_n)\) 的倍数,所以左式整体为 \(\gcd(a_1,a_2,\dots,a_n)\) 的倍数,从而 \(\gcd(a_1,a_2,\dots,a_n)\mid c\)

Task 2:证充分

证明:当 \(\gcd(a_1,a_2,\dots,a_n)\mid c\) 时,方程 \(a_1x_1+a_2x_2+\dots+a_nx_n=c\) 有整数解。

证明方法有很多,此处采用第一数学归纳法说明。

1°:当 \(n=2\) 时,成立;(二元裴蜀定理)
2°:当 \(n>2\) 时,假设 \(n-1\) 时成立。则方程 \(a_1x_1+a_2x_2+\dots+a_{n-1}x_{n-1}=k\cdot\gcd(a_1,a_2,\dots,a_{n-1})\) 必有至少一组整数解,记作 \(x_1,x_2,\dots,x_{n-1}\),代入题目中方程可得 \(k\cdot\gcd(a_1,a_2,\dots,a_{n-1})+a_nx_n=c\),而 \(\gcd(a_1,a_2,\dots,a_n)=\gcd(\gcd(a_1,a_2,\dots,a_{n-1}),a_n)\),又因为 \(\gcd(a_1,a_2,\dots,a_n)\mid c\),所以由二元裴蜀定理可证;
3°:由 1°2° 可得原命题成立。


代码如下,时间复杂度 \(O(n\log\max_{i=1}^n a_i)\)

int n;
int a[N]; // 含义见题目

int gcd(int a,int b){ return b?gcd(b,a%b):a; }
void solve(){
    int i;
    for (i=1;!a[i];++i); // 找到第一个非零 a[i]
    int res=abs(a[i]); // 注意取绝对值
    for (++i;i<=n;++i) res=gcd(res,abs(a[i]));
}

0x07 求整系数二元一次不定方程的整数解 ☆

模板题:洛谷 P5656 【模板】二元一次不定方程 (exgcd)

前置知识:扩展欧几里得算法、裴蜀定理

首先考虑方程何时有整数解,由裴蜀定理得当且仅当 \(\gcd(a,b)\mid c\) 时有解。若方程无解,直接输出 -1;若方程有解,令 \(d=\gcd(a,b),c=k\cdot d\),原不定方程转变为 \(ax+by=k\cdot\gcd(a,b)\),两边同除以 \(k\)\(a\cdot\dfrac{x}{k}+b\cdot\dfrac{y}{k}=\gcd(a,b)\),即 \(ax'+by'=\gcd(a,b)\) 的整数解必是原不定方程的整数解。

而对于新方程,利用 Exgcd 可以求出 \(x',y'\),则原不定方程应有一组解为 \(\begin{cases}x_0=kx'=\dfrac{cx_0}{d}\\ y_0=ky'=\dfrac{cy_0}{d}\end{cases}\),尝试寻找其它的解。由于 \(ax+by=(ax+mab)+(by-mab)=a(x+mb)+b(y-ma)\),所以原不定方程的解集应为 \(\{(x,y)\mid x=x_0+\dfrac{b}{d}k,y=y_0-\dfrac{a}{d}k,k\in\Z\}\)

然后考虑是否有正整数解。首先考虑满足 \(x\in\N_+\),即 \(x_0+\dfrac{b}{d}k\ge 1\),解得 \(k\ge\dfrac{(1-x_0)\cdot d}{b}\),又因为 \(k\in\Z\),所以 \(k_{\min}=\Big\lceil\dfrac{(1-x_0)\cdot d}{b}\Big\rceil\)。同时,把 \(x,y\) 看成两个以 \(k\) 为自变量的一次函数,由于 \(\dfrac{b}{d}>0\),所以 \(x\)\(k\) 增大而增大,同理 \(y\)\(k\) 增大而减小,所以当 \(k\) 取最小值时,可以求得 \(x_{\min}=x_0+\dfrac{b}{d}k_{\min},y_{\max}=y_0-\dfrac{a}{d}k_{\min}\)

如果 \(y_{\max}\le 0\),说明 \(y\) 不可能在 \(x\in\N_+\) 的前提下达到正整数,即原不定方程没有正整数解。由于 \(\dfrac{a}{d},k\in\Z\),所以 \(y_{\min}\equiv y_0-\dfrac{a}{d}k\equiv y_0+\dfrac{a}{d}k_{\min}\equiv y_{\max}\pmod{\dfrac{a}{d}}\)。又因为 \(y_{\min}\) 为最小正整数,所以应让其等于 \(y_{\max}\bmod\dfrac{a}{d}\) 吗?可是,当 \(y_0=0\),即 \(\dfrac{a}{d}\mid y_{\max}\) 时,代入上文公式得 \(y_{\min}=0\notin\N_+\),这时应让 \(y_{\min}=\dfrac{a}{d}\) 才符合题意。综上,\(y_{\min}=\begin{cases}\dfrac{a}{d}&,\dfrac{a}{d}\mid y_{\max}\\ y_{\max}\bmod\dfrac{a}{d}&,\dfrac{a}{d}\nmid y_{\max}\end{cases}=(y_{\max}-1)\bmod\dfrac{a}{d}+1\)

而对于 \(y_{\max}>0\) 的情况,我们还需要求出 \(x_{\max}\)。由前文 \(x_{\max},y_{\min}\) 是原不定方程的一组解,代入得 \(ax_{\max}+by_{\min}=c\),解得 \(x_{\max}=\dfrac{c-by_{\min}}{a}\)。同时,解的个数即为 \(k_{\max}-k_{\min}+1=\dfrac{d}{a}(y_0-y_{\min})-\dfrac{d}{a}(y_0-y_{\max})+1=\dfrac{d}{a}(y_{\max}-y_{\min})+1\)

代码如下,单次时间复杂度 \(O(\log\max(a,b))\)

typedef long long ll;
typedef pair<ll,ll> pll;
int gcd(int a,int b){ return b?gcd(b,a%b):a; }
pll exgcd(int a,int b){
    if (b==0) return pll{1,0};
    pll p=exgcd(b,a%b);
    return pll{p.second,p.first-a/b*p.second};
}
void solve(int a,int b,int c){
    int d=gcd(a,b);
    if (c%d){ printf("-1\n");return; }
    pll q=exgcd(a,b);
    ll x=c/d*1ll*q.first,y=c/d*1ll*q.second;
    ll k1=ceil((1.0-x)*d/b);
    ll xmin=x+b/d*k1,ymax=y-a/d*k1;
    ll ymin=((ymax-1)%(a/d)+a/d)%(a/d)+1,xmax=(c-b*ymin)/a,cnt=(ymax-ymin)/(a/d)+1;
    if (ymax<=0) printf("%lld %lld\n",xmin,ymin);
    else printf("%lld %lld %lld %lld %lld\n",cnt,xmin,ymin,xmax,ymax);
}

0x08 模意义下的乘法逆元 ★★

模板题:洛谷 P3811 【模板】模意义下的乘法逆元

一、定义

对于非零整数 \(a,m\),如果存在 \(b\) 使得 \(ab\equiv 1\pmod m\),就称 \(b\)\(a\) 在模 \(m\) 意义下的逆元(inverse)

通俗地说,如果 \(b\equiv\dfrac{1}{a}\pmod p\),则称 \(b\)\(a\) 在模 \(p\) 意义下的逆元。

二、存在条件

当且仅当 \(a,p\) 互素时,\(a\) 在模 \(p\) 意义下有逆元。

证明如下:

Task 1:证条件

证明:当 \(\gcd(a,p)\ne 1\) 时,\(a\) 在模 \(p\) 意义下无逆元。

采用反证法。假设当 \(\gcd(a,p)\ne 1\) 时,\(a\) 在模 \(p\) 意义有逆元。记 \(b\)\(a\) 在模 \(p\) 意义下的逆元,由定义 \(ab\equiv 1\pmod p\)将同余式写成带余除法,设 \(ab=pq+1(q\in\Z)\),两边同除以 \(\gcd(a,p)\)\(\dfrac{a}{\gcd(a,p)}b=\dfrac{p}{\gcd(a,p)}q+\dfrac{1}{\gcd(a,p)}\),由于 \(\dfrac{a}{\gcd(a,p)},\dfrac{p}{\gcd(a,p)}\in\Z\),所以 \(\dfrac{1}{\gcd(a,p)}=\dfrac{a}{\gcd(a,p)}b-\dfrac{p}{\gcd(a,p)}q\in\Z\),所以 \(\gcd(a,p)=1\),与条件矛盾,所以假设不成立,所以命题得证。

Task 2:证存在

证明:当 \(a,p\) 互素时,\(a\) 在模 \(p\) 意义下有逆元。

由下文 三、F2 易证,此却按表不提。

三、求解方法

F1:费马小定理 + 快速幂

限制条件:\(p\) 为素数(费马小定理前提)

前置知识:费马小定理、快速幂

首先,由费马小定理得 \(a^{p-1}\equiv 1\pmod p\),两边同除以 \(a\)\(a^{p-2}\equiv\dfrac{1}{a}\equiv b\pmod p\),所以要求 \(b\) 只需求出 \(a^{p-2}\) 即可。

但是 \(a\)\(p\) 在实际题目中可能数量级较大,\(a^{p-2}\) 极大。由于 \(b\equiv a^{p-2}\pmod p\),我们只需要求出 \(a^{p-2} \bmod p\) 作为 \(a\) 的逆元即可。

代码如下,时间复杂度为 \(O(\log p)\)

int qpow(int a,int b,int p){ // 快速幂
    if (!a && !b) return 1;
    if (b==1) return a%p;
    int x=qpow(a,b/2,p);
    return x*1ll*x%p*((b&1)?a:1)%p;
}
int inv(int a,int p){ return qpow(a,p-2,p); } // 求 a 在模 p 意义下的逆元

F2:扩展欧几里得算法(Exgcd)

前置知识:扩展欧几里得算法

对于 \(a\) 在模 \(p\) 意义下的逆元 \(b\),由 \(b\equiv\dfrac{1}{a}\pmod p\) 可得 \(ab\equiv 1\pmod p\)。可令 \(ab=1-pk\),从而有 \(ab+pk=1=\gcd(a,p)\),利用 Exgcd 可求得其一组解,符合条件。

模板题有些毒瘤题还要求我们的答案在 \((0,p]\cap\Z\) 的范围内,此时由于 \(ab+pk=a(b+pm)+p(k-am)\),我们可以给答案任意加减任意整数个 \(p\),且操作完仍符合题意,所以我们只需取 \(b\bmod p\) 即可。

另外说一下为何取模完后要加 \(p\) 在取模。这是因为 C++ 的 % 运算结果符号与第一个操作数的符号相同。即对于 a%b,若 \(a>0\) 则结果为正,反之若 \(a<0\) 则结果为负,对于这种情况,此时的 a%b 会在 \((-|p|,0]\) 之间,只要在加上 \(p\) 即可。但是对于第一种情况不应该加上 \(p\),所以最好还要取模一下。

代码如下,时间复杂度 \(O(\log\max(a,p))\),但一般优于 F1。

typedef pair<int,int> pii;
pii exgcd(int a,int b){ // 扩欧(扩展欧几里得算法)
    if (b==0) return pii{1,0};
    pii p=exgcd(b,a%b);
    return pii{p.second,p.first-a/b*p.second};
}
int inv(int a,int p){ return (exgcd(a,p).first%p+p)%p; } // 取模使得返回值在 (0,p] 范围之内

F3:线性递推

限制条件:\(p\) 不为合数

进行带余除法,令 \(p=aq+r\ (q\in\Z,r\in[0,a)\cap\Z)\),则 \(aq+r\equiv p\equiv 0\pmod p\),两边同除以 \(ar\)\(\dfrac{q}{r}+\dfrac{1}{a}\equiv 0\pmod p\),即 \(b\equiv\dfrac{1}{a}\equiv-\dfrac{q}{r}\equiv -\big\lfloor\dfrac{p}{a}\big\rfloor\cdot\dfrac{1}{p\bmod a}\pmod p\),而由于 \(p\bmod a<a\),所以其逆元在递推的过程中已经求过。

\(inv_i\) 表示 \(i\) 的逆元,有 \(inv_i=(-\big\lfloor\dfrac{p}{i}\big\rfloor\cdot inv_{p\bmod i})\bmod p=p-(\big\lfloor\dfrac{p}{i}\big\rfloor\cdot inv_{p\bmod i})\bmod p\),边界条件为 \(inv_1=1\),所以从 \(i=2\) 开始循环。但是当 \(i\mid p\) 时,\(0\) 的逆元没有定义,而有 F1 可得 \(inv_i=i^{p-2}\bmod p\),无法实现。从而只有当 \(p\) 为素数或 \(1\),即不是合数时才成立。

代码如下,时间复杂度 \(O(a)\),但可求出 \(1\sim a\) 中所有数字的逆元值。

int inv[N];
void solve(int n,int p){
    inv[1]=1;
    for (int i=2;i<=n;++i) inv[i]=p-(p/i)*1ll*inv[p%i]%p;
    for (int i=1;i<=n;++i) printf("%d\n",inv[i]);
}

拓展:求 \(n!\) 在模 \(p\) 意义下的逆元

由于 \(\dfrac{1}{n!}\equiv\dfrac{1}{1}\cdot\dfrac{1}{2}\cdot\dots\cdot\dfrac{1}{n}\),所以在用 F3 求出 \(1\sim n\) 的所有数在模 \(p\) 意义下的逆元后,全部相乘即可得到 \(n!\) 在模 \(p\) 意义下的逆元。

代码如下,时间复杂度 \(O(n)\)

int inv[N];
int solve(int n,int p){
    inv[1]=1;
    for (int i=2;i<=n;++i) inv[i]=p-(p/i)*1ll*inv[p%i]%p;
    int res=1;
    for (int i=1;i<=n;++i) res=res*1ll*inv[i]%mod;
    return res;
}

0x09 算数基本定理/整数唯一分解定理、约数基本定理、约数和定理

内容:对于 \(\forall n\in\N_+\),必有 \(n=p_1^{\alpha_1}p_2^{\alpha_2}\dots p_k^{\alpha_k}\ (p_i\) 为素数且 \(p_1<p_2<\dots<p_k,\alpha_i\in\N\),其中 \(1\le i\le n)\)

注:下文定理中 \(n\) 都写成上文中的标准素因数分解式。

约数基本定理:对于 \(\forall n\in\N_+\),其正约数个数为 \(\prod_{i=1}^k(\alpha_i+1)\)

约数和定理:对于 \(\forall n\in\N_+\),其正约数和为 \(\prod_{i=1}^k\sum_{j=0}^k p_i^j=\prod_{i=1}^k\dfrac{p_i^{\alpha_i+1}-1}{p_i-1}\)

posted @ 2026-07-27 22:01  CodingJuRuo  阅读(10)  评论(0)    收藏  举报