C++编程提升之路第二课——数学

一:前言

众所周知,数学在我们编程当中是非常重要的。一些题目,你用纯暴力肯定会超时,而经过数学优化后复杂度就会降低很多。

二:欧几里得算法

1:介绍

  • 其实这个算法就是 g c d ( a , b ) = g c d ( b , a   m o d   b ) gcd(a,b)=gcd(b,a~mod~b) gcd(a,b)=gcd(b,a mod b) 但是要证明它并不是很容易。

  • 我们可以先证明: g c d ( a , b ) = g c d ( b , a − b ) gcd(a,b)=gcd(b,a-b) gcd(a,b)=gcd(b,ab)

    • 我们设 d = g c d ( a , b ) d=gcd(a,b) d=gcd(a,b) ,则 a = d ⋅ a 1 a=d\cdot a1 a=da1 , b = d ⋅ b 1 b=d\cdot b1 b=db1 g c d ( a 1 , b 1 ) = 1 gcd(a1,b1)=1 gcd(a1,b1)=1

    • a − b = d ⋅ e s a 1 − d ⋅ b 1 = d ⋅ ( a 1 − b 1 ) a-b=d \cdot es a1-d \cdot b1=d \cdot (a1-b1) ab=desa1db1=d(a1b1)

    • 那么 a − b a-b ab b b b 都有一个公因子 d d d,接下来只需证明:

    • g c d ( b 1 , a 1 − b 1 ) = 1 gcd(b1,a1-b1)=1 gcd(b1,a1b1)=1

    • 我们采用反证法

    • b 1 = β ⋅ λ b1=\beta \cdot \lambda b1=βλ, a 1 − b 1 = α ⋅ λ ( λ > 1 ) a1-b1=\alpha \cdot \lambda(\lambda>1) a1b1=αλ(λ>1)

    • a 1 = a 1 − b 1 + b 1 = λ ⋅ ( α + β ) a1=a1-b1+b1=\lambda \cdot (\alpha + \beta) a1=a1b1+b1=λ(α+β)

    • 因为之前我们已经定义 g c d ( a 1 , b 1 ) = 1 gcd(a1,b1)=1 gcd(a1,b1)=1 b 1 = λ ⋅ β b1=\lambda \cdot \beta b1=λβ

    • 那么 λ \lambda λ只能等于1

    • 与命题不符,得证

  • ∵ g c d ( a , b ) = g c d ( b , a − b ) \because gcd(a,b)=gcd(b,a-b) gcd(a,b)=gcd(b,ab)

  • ∴ g c d ( a , b ) = g c d ( b , a − b ) = g c d ( b , a − 2 b ) = g c d ( b , a − 3 b ) . . . \therefore gcd(a,b)=gcd(b,a-b)=gcd(b,a-2b)=gcd(b,a-3b)... gcd(a,b)=gcd(b,ab)=gcd(b,a2b)=gcd(b,a3b)...

  • ∴ g c d ( a , b ) = g c d ( b , a   m o d   b ) \therefore gcd(a,b)=gcd(b,a~mod~b) gcd(a,b)=gcd(b,a mod b)

  • 得证!

  • 根据此定理,我们可以快速求出两个数的 g c d gcd gcd

2:code

  • 代码:
#include<bits/stdc++.h>
using namespace std;
inline int gcd(int n,int m){
	if(b==0)return a;
	return gcd(b,a%b);
}
int main(){
	int n,m;
	cin>>n>>m;
	cout<<gcd(n,m);
	return 0;
}

三:扩展欧几里得算法

1:介绍

  • 它就是求 a x + b y = g c d ( a , b ) ax+by=gcd(a,b) ax+by=gcd(a,b)
    • 显然,当 b = 0 b=0 b=0 时,则原方程变为 a x = a ax=a ax=a,解为 { x = 1 y = 0 \begin{cases} x=1\\ y=0\\ \end{cases} {x=1y=0
    • b ≠ 0 b\ne0 b=0,且有 x 1 , y 1 x_1,y_1 x1,y1满足 a   m o d   b ⋅ x 1 + b ⋅ y 1 = g c d ( b , a   m o d   b ) a~mod~b \cdot x_1+b \cdot y1=gcd(b,a~mod~b) a mod bx1+by1=gcd(b,a mod b),则有解 { x = x 1 y = y 1 − x 1 ⋅ f l o o r ( a / b ) \begin{cases} x=x_1\\y=y_1-x_1 \cdot floor(a/b)\\\end{cases} {x=x1y=y1x1floor(a/b)
    • 下面为证明过程
      • ∵ ( a   m o d   b ) ⋅ x 1 + b ⋅ y 1 = g c d ( b , a   m o d   b ) \because(a~mod~b)\cdot x_1+b \cdot y_1=gcd(b,a~mod~b) (a mod b)x1+by1=gcd(b,a mod b)
      • ∴ ( a − f l o o r ( a / b ) ⋅ b ) ⋅ x 1 + b ⋅ y 1 = g c d ( a , b ) \therefore(a-floor(a/b)\cdot b) \cdot x_1+b\cdot y_1=gcd(a,b) (afloor(a/b)b)x1+by1=gcd(a,b)
      • ∴ a ⋅ x 1 + b ⋅ ( y 1 − x 1 ⋅ ( f l o o r ( a / b ) ) = g c d ( a , b ) \therefore a\cdot x_1+b\cdot (y_1-x_1\cdot (floor(a/b))=gcd(a,b) ax1+b(y1x1(floor(a/b))=gcd(a,b)
      • 证毕
    • 根据此结论,我们可以一直递归,直至 b = 0 b=0 b=0

2:code

  • 代码:
#include<bits/stdc++.h>
using namespace std;
void ex_gcd(int a,int b,int &x,int &y){//求a*x+b*y=gcd(a,b)的整数解 
	if(!b){
		x=1;
		y=0;
		return;
	}
	ex_gcd(b,a%b,y,x);
	y-=(a/b)*x;
}
int main(){
	int a,b,x,y;
	cin>>a>>b;
	ex_gcd(a,b,x,y);
	cout<<x<<' '<<y;
	return 0;
}

3:拓展1

  • 题目:解方程 a x + b y = c ax+by=c ax+by=c
    • 思路:我们设 d = g c d ( a , b ) d=gcd(a,b) d=gcd(a,b) ,再把等式两边同时乘上 d c \frac{d}{c} cd
    • 则原方程化为 a ⋅ d c ⋅ x + b ⋅ d c ⋅ y = g c d ( a , b ) a\cdot \frac{d}{c} \cdot x+b\cdot\frac{d}{c}\cdot y=gcd(a,b) acdx+bcdy=gcd(a,b)
    • 求解一个 a ⋅ x ′ + b ⋅ y ′ = g c d ( a , b ) a\cdot x'+b\cdot y'=gcd(a,b) ax+by=gcd(a,b)
    • 则方程的解为 { x = x ′ ⋅ d c y = y ′ ⋅ d c \begin{cases} x=x'\cdot\frac{d}{c}\\ y=y'\cdot\frac{d}{c}\\ \end{cases} {x=xcdy=ycd
    • c ∤ d c\nmid d cd,则原方程无解

4:拓展2

  • 题目:解方程 a ⋅ x ≡ b ( m o d p ) a \cdot x \equiv b \pmod p axb(modp)
    • 思路:此题非常简单,将它化为拓展1
    • 原方程化为 a x + p y = b ax+py=b ax+py=b 再用拓展1解即可
    • b = 1 b=1 b=1 ,则此题化为了求解 a a a m o d   p mod~p mod p 意义下的逆元。

四:逆元的求法

  • 逆元的定义为若 a ⋅ x ≡ b ( m o d p ) a \cdot x \equiv b \pmod p axb(modp)
  • 求解方式有一下几种:

1:费马小定理(p为质数)

  • 费马小定理: a p − 1 ≡ 1 ( m o d p ) a^{p-1} \equiv 1 \pmod p ap11(modp),证明此处省略
  • a ⋅ a p − 2 ≡ 1 ( m o d p ) a\cdot a^{p-2} \equiv 1 \pmod p aap21(modp)
  • ∴ a \therefore a a m o d   p mod~p mod p意义下的逆元为 a p − 2 a^{p-2} ap2
  • 只需使用一个快速幂就好了
  • 代码:
#include<bits/stdc++.h>
using namespace std;
int power(int a,int b,int p){
	int ans=1;
	while(b){
		if(b&1)ans=ans*a%p;
		a=a*a%p;b>>=1;
	}
	return ans;
}
int inv(int x,int p){
	return power(x,p-2,p);
}
int main(){
	int x,p;
	cin>>x>>p;
	cout<<inv(x,p);
	return 0;
}

2:线性求逆元(p为质数)

  • 当我们要求 1 1 1 n n n 中所有的数的逆元
  • 我们用一个 i n v [ i ] inv[i] inv[i] 来存 i i i 的逆元
  • 设我们要求的是 i n v [ i ] inv[i] inv[i] ,且 p = k ⋅ i + r ( r < i ) p=k\cdot i+r(r<i) p=ki+r(r<i)
  • 又因为 p p p 为质数,则 0 < r < i 0<r<i 0<r<i
  • k = ⌊ x ⌋ , r = p   m o d   i , k × i + r ≡ 0 ( m o d p ) k=\lfloor x \rfloor,r=p~mod~i,k\times i+r \equiv 0\pmod p k=x,r=p mod i,k×i+r0(modp)
  • 将等式两边同时乘以 i , r i,r i,r 的逆元
  • k ⋅ i n v [ r ] + i n v [ i ] ≡ 0 ( m o d p ) ⇒ i n v [ i ] ≡ − k ⋅ i n v [ r ] ( m o d p ) d ⇒ i n v [ i ] ≡ ⌊ p i ⌋ ⋅ i n v [ p   m o d   i ] ( m o d p ) k\cdot inv[r]+inv[i] \equiv 0\pmod p\Rightarrow inv[i]\equiv -k\cdot inv[r] \pmod pd\Rightarrow inv[i]\equiv \lfloor \frac{p}{i} \rfloor \cdot inv[p~ mod~i] \pmod p kinv[r]+inv[i]0(modp)inv[i]kinv[r](modp)dinv[i]ipinv[p mod i](modp)
  • ∴ i n v [ i ] = ( p − ⌊ p i ⌋ ⋅ i n v [ p   m o d   i ] ) \therefore inv[i]=(p- \lfloor \frac{p}{i} \rfloor \cdot inv[p~ mod~i] ) inv[i]=(pipinv[p mod i])
  • 根据此递推式,即可写出代码
#include<bits/stdc++.h>
using namespace std;
const int N=3e6+7;
int n,p;
long long inv[N];
int main(){
	cin>>n>>p;
	inv[1]=1;
	for(int i=2;i<=n;i++)
	inv[i]=(p-p/i)*inv[p%i]%p;
	for(int i=1;i<=n;i++){
		cout<<inv[i]<<"\n";
	}
	return 0;
}

3:扩展欧几里得求逆元

  • 根据我们之前学的扩展欧几里得拓展2的例题:
  • a ⋅ x ≡ b ( m o d p ) a \cdot x \equiv b \pmod p axb(modp)
  • 将b换为1即可
#include<bits/stdc++.h>
using namespace std;
void ex_gcd(int a,int b,int &x,int &y){//求a*x+b*y=gcd(a,b)的整数解 
	if(!b){
		x=1;
		y=0;
		return;
	}
	ex_gcd(b,a%b,y,x);
	y-=(a/b)*x;
}
int get_inv(int a,int p){
	int x,y;
	ex_gcd(a,p,x,y);
	x=(x%p+p)%p;//求最小的逆元
	return x; 
} 
int main(){
	int a,p;
	cin>>a>>p;
	cout<<get_inv(a,p);;
	return 0;
}

5:结语

想必通过今天作者对大家的总结,大家的数学水平都应该有一定的提升了吧,写code的能力估计也强了好几倍,希望在这段时间了,我们一起努力,一起进步!有什么错误还请各位大佬指出。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值