进阶数论

TonyYin

2020-02-11 09:50:33

Personal

# 进阶数论 # By YYWD ## www.bjeaedu.com/ 部分资料来自www.jisuanke.com ## 扩展欧几里得 ### 概述 扩展欧几里得算法是用来在已知$a,b$的情况下求解一组$x,y$,使满足$ax+by=gcd(a,\,b)=d$.($gcd$为最大公因数,方程一定有解) ### 特解 要计算$gcd(a,\,b)$,并求出满足条件的$x,y$. 将下一个状态 $b$ 和 $(a\%b)$ 表示为:$b\cdot x_1+(a\%b)\cdot y_1=d$ 则: $$ \because b\cdot x_1+(a\%b)\cdot y_1=d, $$ 又 $$\because a\%b=a-\left\lfloor\frac{a}{b}\right\rfloor\times b $$ $$ \begin{aligned} \therefore gcd(a,\,b)=d & =b\cdot x_1+(a-\left\lfloor\frac{a}{b}\right\rfloor\times b)\cdot y_1\\ &=b\cdot x_1+a\cdot y_1-\left\lfloor\frac{a}{b}\right\rfloor\times b\times y_1\\ &=a\times y1+b\times(x_1-\left\lfloor\frac{a}{b}\right\rfloor\times y_1) \end{aligned} $$ 所以满足条件的$x=y_1,\,y=x_1-\left\lfloor\frac{a}{b}\right\rfloor\times y_1$ ~~~cpp int exgcd(int a, int b, int &x, int &y) {//函数返回值为gcd(a,b) if(b == 0) { x = 1;//gcd(a,b) = a = 1a+0b y = 0; return a; } int r = exgcd(b, a % b, x, y);//先递归 int t = x;//再计算x,y x = y; y = t - a / b * y; return r; } ~~~ ### 通解 假设特解为$(x_0,y_0)$,满足$ax_0+by_0=gcd(a,\,b)=d$ 则有 $$ \begin{aligned} a(x_0+k\frac{b}{d})+b(y_0-k\frac{a}{d})=d \end{aligned} $$ (k是整数) 所以方程的通解为: $$ \begin{aligned} x=x_0+k\frac{b}{d} \\ y=y_0-k\frac{a}{d} \end{aligned} $$ 对于其它二元一次不定方程$ax+by=c$($c$为任意正整数)只有当$d\mid c$时才有整数解,通解形式: $$ \begin{aligned} x=\frac{c}{d}x_0+k\frac{b}{d}\\y=\frac{c}{d}y_0-k\frac{a}{d} \end{aligned} $$ ## 同余方程(模方程) ### 定义: 指形如$ax\equiv b\pmod c$的一种方程。其中$a,b,c$是参数,$a,c$是正整数,$b$是小于$c$的非负整数,$x$是未知数。 ### 扩展欧几里得解同余方程 根据同余定义,$ax\equiv b\pmod c$ 可化为 $xa+kc=b$. 其中$a,b,c$已知,$x,k$未知,所以能用扩展o欧几里得解同余方程。 ### 通解 如果$b\nmid gcd(a,c)$,方程无整数解. 否则同扩展欧几里得酸法,求得通解: $$ x=\frac{b}{d}x_0+k\frac{c}{d}\\k=\frac{b}{d}p_0-k\frac{a}{d} $$ 其中k为整数 ### 最小非负整数解 求$x$的最小非负整数解 因为 $x=\frac{b}{d}x_0+k\frac{c}{d}$,其中$x_0$是方程$xa+kc=gcd(a,\,c)$的特解, $\frac{b}{d}x_0$ 是原同余方程的特解, 若$x>0$,则有$x\equiv \frac{b}{d}x_0 \pmod{ \frac{c}{d}} $。最小非负整数解为$\frac{b}{d}x_0\,\%\,\frac{c}{d}$. 若$x<0$,同理,最小非负整数解为$(\frac{b}{d}x_0\bmod \frac{c}{d}+\frac{c}{d} )\bmod\frac{c}{d}$ 综上,最小非负整数解=$(\frac{b}{d}x_0\bmod\frac{c}{d}+\frac{c}{d})\bmod \frac{c}{d}$ ~~~cpp int exgcd(int a, int b, int &x, int &y) { if(b == 0) { x = 1; y = 0; return a; } int r = exgcd(b, a % b, x, y); int t = x; x = y; y = t - a / b * y; return r; } int main() { int a,b,c,x,p; cin>>a>>b>>c; int d=exgcd(a,c,x,p); if(b%d!=0){ cout<<"No Solution"<<endl; } else{ cout<<(b/d*x%(c/d)+c/d)%(c/d)<<endl; } return 0; } ~~~ ## 逆元 ### 乘法逆元 给定两个整数$a$和$p$,假设存在$x$使得$ax\equiv 1\pmod p)$.称 $x$ 为 $a$ 关于 $p$ 的**乘法逆元**。$a$关于$p$的逆元存在的充分必要条件是$gcd(a,p)=1$ ### 求一个数的逆元 将上面式子变形,变成$ax+kp=1$,用扩展欧几里得能够求出一个$x$. 如果$p$是质数,有另一种方法求$a$关于$p$的逆元: $$ a^{p-1}\bmod p=(a^{p-2}\times a)\bmod p=(x\times a)\bmod p=1 $$ 所以$a$关于$p$的逆元是 $a^{b-2}\bmod b$ 的值,用快速幂优化。 ~~~cpp int exgcd(int a, int b, int &x, int &y) { if(b == 0) { x = 1; y = 0; return a; } int r = exgcd(b, a % b, x, y); int t = x; x = y; y = t - a / b * y; return r; } int pow_mod(int x, int n, int mod) { int res = 1, temp = x; for (; n; n /= 2) { if (n & 1) { res = res * temp % mod; } temp = temp * temp % mod; } return res; } bool is_prime(int x) { if (x == 1) { return false; } for (int i = 2; i * i <= x; i++) { if (x % i == 0) { return false; } } return true; } int main() { int a,b; cin>>a>>b; int x,y; int d=exgcd(a,b,x,y); if(d!=1){ cout<<"No Solution"<<endl; } else{ cout<<(x+b)%b<<endl; //方法一 } if(is_prime(b)){ cout<<pow_mod(a,b-2,b)<<endl;//方法2(p为质数时才能用) } return 0; } ~~~ ### 线性预处理逆元 有些情况,需要用到区间内的逆元。对于$\leq p-1$的正整数$n$,可以用$\mathcal{O}(n)$复杂度求解$1-n$的逆元。(用 $inv_i$表示 $i$ 关于 $p$ 的逆元) 方法: $$ inv_i=(p-\left\lfloor\frac{p}{i}\right\rfloor)inv_{p\%i}\%p $$ 证明:要证$i\cdot inv_i\%p=1$ 计蒜客方法: $$ \begin{aligned} i\cdot inv_i\%p&=i\times (p-\frac{p}{i})inv_{p\%i}\%p\\ &=i\times(p-\frac{p-p\%i}{i})inv_{p\%i}\%p\\ &=p\%i\times inv_{p\%i}\%p\\ &=1 \end{aligned} $$ 证明方法: 设$p=ki+r$, 则有: $$ \begin{aligned} ki+r&\equiv 0\pmod p\quad\quad\quad (2)\\ (1)\times inv_i\times inv_r&=(2),\\ k\cdot inv_r+inv_i&\equiv0\pmod p\quad\quad\quad(2)\\ \because p&=ki+r\\ \therefore k=\left\lfloor\frac{p}{i}\right\rfloor&,r=p\%i\\ \therefore \left\lfloor\frac{p}{i}\right\rfloor inv_{p\%i}+inv_i&\equiv0\pmod p\\ \therefore -\left\lfloor\frac{p}{i}\right\rfloor inv_{p\%i}&\equiv inv_i\pmod p\\ \therefore (p-\left\lfloor\frac{p}{i}\right\rfloor) inv_{p\%i}&\equiv inv_i\pmod p \end{aligned} $$ 实现: ~~~cpp // p 必须为质数,p / i 为整除。 inv[1] = 1; for (int i = 2; i <= n; ++i) { inv[i] = (p - p / i) * inv[p % i] % p; } ~~~ ### 组合数取模 设$invs_i=inv_1\times inv_2\times \cdots \times inv_i\%p,fact_i=1\times 2\times 3\times \cdots\times i\%p$ $C_n^m\%p=\frac{n!}{m!(n-m)!}\%p=fact_n\cdot invs_m\cdot invs_{n-m}\%p$ 实现: ~~~cpp fac[0]=1; for(int i=1;i<=n;i++){ fac[i]=fac[i-1]*i%p; } inv[1]=1; for(int i=2;i<=n;i++){ inv[i]=(p-p/i)*inv[p%i]%p; } inv[0]=1; for(int i=1;i<=n;i++){ inv[i]=inv[i]*inv[i-1]%p; } cout<<fac[n]*inv[m]*inv[n-m]%p<<endl; ~~~ ## 中国剩余定理 ### 内容 中国剩余定理一元同余线性方程组: $$ \begin{aligned}&x\equiv a_1\pmod {m_1}\\&x\equiv a_2\pmod {m_2}\\&\cdots\\&x\equiv a_n\pmod {m_n}\\\end{aligned} $$ 假设整数$m_1,m_2,\cdots,m_n$**两两互质**,则方程组有解,通解如下: 设$M=m_1\times m_2\times \cdots\times m_n=\prod m_i$,并设$M_i=M/m_i$, $t_i=M_i^{-1}$. $t_i$表示$M_i$模$m_i$意义下的逆元,即$M_it_i\equiv1\pmod m_i$. 通解为: $$ x=a_1t_1M_1+a_2t_2M_2+\cdots+a_nt_nM_n+jM=\sum_{i-1}^{n}a_it_iM_i+kM $$ ### 实现 程序输出的是最小非负整数解 ~~~cpp #include <iostream> using namespace std; int exgcd(int a, int b, int &x, int &y) { if (b == 0) { x = 1; y = 0; return a; } int r = exgcd(b, a % b, x, y); int t = x; x = y; y = t - a / b * y; return r; } int a[101], m[101]; int CRT(int n){ int M=1; int ans=0; for(int i=1;i<=n;i++){ M*=m[i]; } for(int i=1;i<=n;i++){ int x,y; int Mi=M/m[i]; exgcd(Mi,m[i],x,y); ans=(ans+Mi*x*a[i])%M; } if(ans<0){//最小非负整数解就是在模 M 意义下进行求解, //如果答案是负数就加 M 变成非负数,最后输出答案 ans+=M; } return ans; } int main() { int n; cin>>n; for(int i=1;i<=n;i++){ cin>>m[i]>>a[i]; } cout<<CRT(n)<<endl; return 0; } ~~~ ## 一般同余线性方程组 ### 概述 由若干个同余方程组成的方程组叫做同余方程组$\ldots\ldots$<font size=2>废话</font> 与中国剩余定理的区别:模数不一定互质 ### 算法 考虑两个同余方程: $$ \begin{aligned}x\equiv a_1(mod\;m_1)\\x\equiv a_2(mod\;m_2)\end{aligned} $$ 可以写为: $$ \begin{aligned}x+k_1m_1=a_1\quad\quad\quad(1)\\ x+k_2m_2=a_2\quad\quad\quad(2)\end{aligned} $$ $(1)-(2)$得到: $$ m_1k_1-m_2k_2=a_1-a_2 $$ 其中$m1,m2,a1,a2$已知,发现方程形如$ax+by=c$. 所以方程有解条件是$gcd(m1,m2)\mid (a_1-a_2)$ 可以用扩展欧几里得算法求得$k_1$的最小非负整数解$K$,通解为: $$ k_1=K+\frac{m2}{gcd(m_1,m_2)}\times C $$ 其中$C$为整数. 设$d=gcd(m_1,m_2)$,对方程两边除以$d$,得到: $$ \frac{m_1k_1}{d}=\frac{a_1-a_2}{d}+\frac{m_2}{d}\times k_2 $$ 写成同余方程的形式: $$ \frac{k_1m_1}{d}\equiv \frac{a_1-a_2}{d}(mod\;\frac{m_2}{d}) $$ 同余方程两边同乘$d$: $$ k_1m_1\equiv a_1-a_2(mod\;\frac{m_2}{d}) $$ 将$k_1$的通解$k_1=K+\frac{m2}{d}\times C$代回方程$x+k_1m_1=a_1$,得到: $$ x=a_1-Km_1-\frac{m_1m_2C}{d} $$ 写成同余方程的形式: $$ x\equiv a_1-Km_1(mod\;\frac{m_1m_2}{d}) $$ 这样把两个同余方程合并成一个。同理可以将$n$个方程组成的同余方程组化为一个同余方程,最后用扩展欧几里得算法求解$x$。 ### 实现 ~~~cpp #include <iostream> using namespace std; int exgcd(int a, int b, int &x, int &y) {//ax+by=d; if (b == 0) { x = 1; y = 0; return a; } int r = exgcd(b, a % b, x, y); int t = x; x = y; y = t - a / b * y; return r; } int a[101], m[101]; int exCRT(int a[],int m[],int n){ int M=m[1],A=a[1],x,y,d; //a是余数数组,m是模数数组,n是方程数量 //M记录合并后方程的模数,A是合并后方程的余数x是M特解,y是mi特解 for(int i=2;i<=n;i++){ d=exgcd(M,m[i],x,y); if((A-a[i])%d!=0){ return -1; } x=((A-a[i])/d*x%(m[i]/d)+m[i]/d)%(m[i]/d);//最小非负整数解 A-=x*M;//新的余数就是原来的余数减去x倍原来的模数再对新的模数取余 M=M/d*m[i];//新的模数是原来的模数/d*m[i]; A=(A%M+M)%M;//防止A为负数 } return A; } int main() { int n; cin>>n; for(int i=1;i<=n;i++){ cin>>m[i]>>a[i]; } cout<<exCRT(a,m,n)<<endl; return 0; } ~~~ ## 练习题 | **算法** | **题号** 点击可跳转 | | ------------------ | ------------------------------------------------------------ | | 扩展中国剩余定理 | [$\color{#9d3dcf}{P4777}$](https://www.luogu.com.cn/problem/P4777) | | 中国剩余定理 | [$\color{#53c41a}{P1495}$](https://www.luogu.com.cn/problem/P1495) | | 矩阵快速幂 | [$\color{#ffc116}{P3390}$](https://www.luogu.com.cn/problem/P3390) | | 区间预处理乘法逆元 | [$\color{#ffc116}{P3811}$](https://www.luogu.com.cn/problem/P3811) | | 扩展欧几里得算法 | [$\color{#53c41a}{P1082}$](https://www.luogu.com.cn/problem/P1082) | | 矩阵快速幂 | [$\color{#53c41a}{P1962}$](https://www.luogu.com.cn/problem/P1962) | | 扩展欧几里得算法 | [$\color{#3498db}{P1516} $](https://www.luogu.com.cn/problem/P1516) | | 中国剩余定理 | [$\color{#3498db}{P1495}$](https://www.luogu.com.cn/problem/P1495) | ### 子段乘积 [$\textcolor{red}{Click\;\;\; here}$](https://ac.nowcoder.com/acm/contest/3005/C) 给出一个长度为 n 的数列 $a_1,a_2,\ldots,a_n$ 求其长度为 $k$ 的连续子段的乘积对 $998244353$ 取模余数的最大值。