同余-费马小定理-乘法逆元与线性同余方程

update1: 初等数论部分(是对下面拓展欧几里得算法的铺垫):

update2:由于第一开始学习理解不够深入,出现众多错误,现在看来真是误人子弟(实在太烂了),现在修改了一些错误,同时润滑了一下语言。

线性方程 ax +by =gcd(a,b)的解:

假设 特解(x0,y0) 是方程组的一组解,d=gcd(a,b),那么通解就是 x =x0+b/d*k , y=y0-a/d*k;

例如 10x+35y =5,的一组特解(-3,1),那么通解可表示为(-3+7*k,1-2*k),带入之后显然满足方程,那么我们就可以构造出无穷无尽组解符合原来的方程。

1.gcd函数既满足交换率也满足结合律

  gcd(a,b,c)=gcd(a,b-a,c-b);

证明: 假设 d=gcd(a,b,c),那么 d|a,d|b,d|c  => d|(b-a) ,  d|(c-a)  所以d 是 (a,b-a,c-a) 的一个公约数 那么d<=gcd(a,b-a,c-b)   =>   gcd(a,b,c)<=gcd(a,b-a,c-b);

假设 d1=gcd(a,b-a,c-b),那么 有 d1 |a,d1 |(b-a),d1|(c-b) => d1(b-a+a)=> d1|b 同理 d1|c ,所以d1是 a,b,c 的一个公约数,所以 d1<=gcd(a,b,c) => gcd(a,b,c)>=gcd(a,b-a,c-b);

 所以 gcd(a ,b-a ,c-b)=gcd(a,b,c) 得证。

同余 :如果说 a 、b 除以m 的余数相同,那么说 a 与 b 模 m 同余,记作 a≡b(m)。

整除符号是 “ | ”

费马小定理: 如果p 是质数 那么对于任意的正整数 a 都有 a^p≡a(p) 也就是说 如果a 是质数那么a的任意整数幂 与 a 模p 同余 。

乘法逆元的定义:

如果说a与b互质且b能够整除a 那么存在一个整数x 使得a/b ≡a*x(m) 则称x 为b 的模 m 的乘法逆元。记作b逆(m);(如果说b ,m 不是互质的,乘法逆元不存在);

首先我们用p代表模数m.

证明 1:当p 是质数是 b逆=b ^(p-2)

有乘法逆元的定义可知 a/b≡a*b逆(p)两边同是乘以b可得 a≡a*b*b逆(p)

整理得 b*b逆≡1(p)因为p是质数满足费马小定理使用条件则,b^p≡b(p);

先把两边同时乘以b 得 b^2*b逆≡b(p)=> b^2*b逆≡b^p(p);

此时两边在同除以b^2;

b逆=b^(p-2);

此时借助据快速幂就可以在log(p)的时间复杂度内求出逆元

快速幂求逆元:

//情景:给定 n 组 ai,pi,其中 pi 是质数,求 ai 模 pi 的乘法逆元,若逆元不存在则输出 impossible。
/*什么时候无解呢,当a是p的倍数是,一定是无解的,因为当a是p的倍数时,a%p==0定义时就不满足了,除此之外,我们都可以用费马小定理构造出逆元*/


#include<bits/stdc++.h>
using namespace std;
#define int long long 
int qp(int a,int b,int p){
    int res=1;
    for(;b;b>>=1){
        if(b&1) res=(res*a)%p;
        a=(a*a)%p;
    }
    return res;
}
signed main(){
    int tt;
    cin>>tt;
    while(tt--){
        int a,p;
        cin>>a>>p;
        int res=qp(a,p-2,p);
        if(a%p==0)cout<<"impossible"<<endl;
        else cout<<res<<endl;
    }
    return  0;
}

推理二 :如果仅保证b ,m 互质的情况下乘法逆元可以通过求解同余方程 b*x≡1(p)的解

如果我们在计算过程中遇到了a/b这样的分式计算也可以先把a,b同时对p 取模 再把 a*b逆%p 作为计算的结果,是结果变成一个整数,当然这样做的前提是b 与 p 是互质的,一般题目会强调这一点。

那么现在问题就来到了如何求解同余方程了:

线性方程的引入:

裴属定理:对于任意一对正整数a,b ,一定存在整数x,y使得ax +by =Gcd(a,b);

证明:因为a 是最大公约数的倍数,b 是最大公约数的倍数 ,所以(a*x+b*y)也是最大公约数的倍数。

ex_gcd拓展欧几里得算法:就是解决上述方程的算法。

顾名思义,拓展欧几里得算法的老爹就是欧几里得算法

1) 欧几里得算法:求a与 b的最大公约数 GCD ;

证明1:gcd(a,b)=gcd(b,a%b);

“|” 为整除符号

推论1:设d为a,b的最大公约数,则有d |a,d | b ,那么一定有 d |(x*a+y*b);

反过来如果一个数及能整除a又能整除b那么 这个数一定是a与b的公约数。

 因为p|a,p|b,因为a%b=a-a/b*b 我们把a/b看作常数c 那么a%b=a-c*b

所以根据推论1:p|(a-c*b)就是说 p|(a%b);

也就是说,p也是a与a%b 的公约数,所以 gcd(a,b)=gcd(b,a%b)

因为a%b<b,经过多次操作,就能求出a与b的最大公约数。

特别的gcd(0,a)=a;

2)再来看看拓展欧几里得算法:
 若要求线性方程 :ax +by =Gcd(a,b):

设d 为a,b的最大公约数;

1>当b 等于零时:  x=1,y=0,顺便带出gcd(a,0)=a;

2>当b 不等于零时:

有欧几里得算法gcd(a,b)=gcd(b,a%b)=d 可知 (a%b)*x+b*y=d;

由于 取模性质  a%b =a-a/b *b (除法默认下取整)

那么 (a-a/b) x+b*y=d  => ax+b(y-ax/b )=d    (略去乘号)

化简得 a*x +b*(y-a/b *x)=d;观察一下 这不就是 ax+by=d 的变形吗。

x不变,我们只需把 (y-a/b*x)看作 y 相当于递归求解 线性方程 ax +by =Gcd(a,b)由于 y递减,最中一定可以得到答案。

因此可以采用递归算法先求出下一层的x和y 在利用上述公式回代即可;

//给定 n 对正整数 ai,bi,对于每对数,求出一组 xi,yi,使其满足 ai×xi+bi×yi=gcd(ai,bi)。
//
//输入格式
//第一行包含整数 n。
//
//接下来 n 行,每行包含两个整数 ai,bi。
#include<bits/stdc++.h>
using namespace std;

int  exgcd(int a,int b,int &x,int &y){
	if(b==0){
		x=1,y=0;
		return a;
	}
	
	int d=exgcd(b,a%b,y,x);
	y-=a/b*x;
	return d;	
}
signed main(){
    int tt;
    cin>>tt;
    while(tt--){
	    int a,b,x,y;
		cin>>a>>b;
		exgcd(a,b,x,y);
		cout<<x<<" "<<y<<endl;
	}

    return 0;
}

878. 线性同余方程
 

给定 n 组数据 ai,bi,mi,对于每组数求出一个 xi,使其满足 ai×xi≡bi(modmi),如果无解则输出 impossible。

输入格式
第一行包含整数 n。

接下来 n 行,每行包含一组数据 ai,bi,mi。

输出格式
输出共 n 行,每组数据输出一个整数表示一个满足条件的 xi,如果无解则输出 impossible。

每组数据结果占一行,结果可能不唯一,输出任意一个满足条件的结果均可。

输出答案必须在 int 范围之内。

数据范围
1≤n≤e5,
1≤ai,bi,mi≤2×e9
输入样例:
2
2 3 6
4 3 5
输出样例:
impossible
-3

启发:该问题可以转变成一个乘法逆元问题;

根据同余方程的性质:

 ai *xi ≡bi (mod mi) =>  ai*xi=mi*y+bi   =>   ai*xi-mi*y=bi   =>  ai*xi+mi(-y)=bi;

对于每组 样例求解 ax+m (-y)=b;

这样就完美的把一个同余方程转化成了线性方程,但这还不够。

我们如果想利用拓展欧几里得算法求解线性方程ax +by =Gcd(a,b)

必须把等式的右边化成 gcd(a,b);

我们设 d=gcd(a,b);

于是我们想到 a*(x*d/b)+m*(-y*d/b)=b*d/b  =>从而 a*(x*d/b)+m*(-y*d/b)=d;

正是因为这个变形,等式变得不一定有解了,本来根据裴属定理,如果x ,y 都是整数,方程是一定有整数解的,现在经过变形,对应原方程类x,类y 就不是整数了,线性方程的解就和 b%d 有关了,这是因为只有b%d==0,x*d/b、y*d/b 才会是整数,会有整数解,否则才就算扩大若干倍答案总会出现非整数。

我们用拓展欧几里得算法求解出这个线性方程组的解 就是 x*d/b

把这个解乘以 b/d 就是真正的x 的值了

为了方便简化方程为 ax+my’=b ,只要b能整出除d,那么 方程一定有整数解。

注:题目中并没有问我们y 的取值,我们就无需理会y 的符号所以这个程序把 -y 直接看成y 这对结果x的值没有影响,此外还用到了整数取模公式,为了避免爆int 开了long long

#include<bits/stdc++.h>
using namespace std;
#define int long long 
int exgcd(int a,int b,int &x,int &y){
	if(!b) {
		x=1,y=0;
		return a;
	}
	int d=exgcd(b,a%b,y,x);
	y-=a/b*x;
	return d;
}
signed main(){
    int tt;
    cin>>tt;
    while(tt--){
		int a,b,m;
		cin>>a>>b>>m;
		int x,y;
		int d=exgcd(a,m,x,y);
		if(b%d) puts("impossible");
		else cout<<(b/d*x%m+m)%m<<endl;
	}
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

litian355

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值