性质:
1.反身性:a≡a (mod m);
2.对称性:若a≡b(mod m),则b≡a (mod m);
3.传递性:若a≡b(mod m),b≡c(mod m),则a≡c(mod m);
4.同余式相加:若a≡b(mod m),c≡d(mod m),则a c≡b d(mod m);
5.同余式相乘:若a≡b(mod m),c≡d(mod m),则ac≡bd(mod m)。
先回顾一下欧几里得算法:
gcd(a,b)=gcd(b,a%b)
int gcd(int a,int b) //最大公约数
{
if(b==0) return a;
else return gcd(b,a%b);
}
int lcm(int a,int b) //最小公倍数
{
return a/gcd(a,b)*b; //防止溢出
}
扩展欧几里得算法:
定义:ax+by=gcd(a,b)=d,在已知a,b的情况下,求解出一组x,y
推导过程:因为a%b=a-(a/b)b,则有 d=bx1+[a-(a/b)b]y1=bx1+ay1-(a/b)by1=ay1+b(x1-a/by1),故 x=y1,y=x1-a/by1
性质:
1.若通过扩展欧几里得求出一组特解(x0,y0),那么有ax0+by0=d.
则方程的通解为,其中k为任意整数
x=x0+k*(b/d)
y=y0-k*(a/d)
2.已知ax+by=d的解,对于ax+by=c的解,c为任意正整数,只有当d|c时才有解
其通解为
x=(c/d)x0+k(b/d)
y=(c/d)y0-k(a/d)
void exgcd(ll a,ll b,ll &d,ll &x,ll &y){
if(b==0){
x=1;y=0;
d=a;
return ;
}
else{
exgcd(b,a%b,d,x,y);
ll temp=x;
x=y;
y=temp-(a/b)*y;
}
}
线性同余方程:
定义:形如:ax≡b (mod n)的方程。
性质:
定理1:此方程对于未知量x有解当且仅当 gcd(a,n) | b
定理2:d=gcd(a,n),若d|b,则方程恰好有d个模n不同余的解,否则方程无解
定理3:若x0是方程的任一解,则该方程对模n有d个不同的解,分别为xi=x0+k*(b/d),(k=1,2,…,d-1)
解线性同余方程:
ax≡b (mod n) —> ax+ny=b d=gcd(a,n),若不满足d|b,则方程无解 否则, ax0+ny0=d,利用扩展欧几里得求出一组特解(x0,y0) 然后,x=x0*(b/d)%n就是原方程的一个解 且其有d个不同的解,为xi=(x+k*(n/d))%n,0<=k< d
int f(int a,int b,int n){
exgcd(a,n,d,x,y);
if(b%d)
return -1;//代表无解
x=x*(b/d)%n;
for(int i=1;i<=d;i++){
ans[i]=(x+(i-1)*(n/d))%n;
}
}
最小整数解:
由于一元线性同余方程的通解可以写成res = ( X + i * (b /d) ) (mod n) = X + i * (n/d) + n * y,由于 y 与 i 均为变量因此可以将其合并得到式子 res = X + y * ( n/d) (其中将原式中的 n * y 看做 n/d * d * y,由于y是变量因此可以将 d*y这个整体看为 y),因此可以得到res = X(mod n/d) ,设m/d 为 t ,其最小正整数解可表示为 (X%t + t) % t。
int ex_gcd(int a,int b,int &x,int &y){
if(b == 0){
x = 1;y = 0;
return a;
}else{
int r = ex_gcd(b,a%b,y,x);
int t = y;
y = x - (a/b)*y;
x = t;
return r;
}
}
void RemainderEquation(int a,int b,int n)
{
int X,Y,d;
long long res;
long long min_res;
d=gcd(a,n);
exgcd(a,n,X,Y);
if(b%d == 0)
{
X = X * (b / d) % n;//得到同余方程一解
for(int i = 0 ; i < d; i++)
{
res = (X + (i * (b/d))) % n;
printf("%lld\n",res); //输出所有解
}
min_res=(X%(n/d)+(n/d))%(n/d);
cout<<min_res<<endl; //输出最小解
}else
{
printf("No Sulutions!\n");
}
}
解一元线性同余方程组:
先以两个方程为例:
----------------------------------(1)
----------------------------------(2)
令a=(,
).
首先此方程组有解的充分必要条件是(,
) | (
-
),此时方程组仅有一个小于a的非负整数解,利用扩展欧几里得算法解出来。
式子(1)等价于
式子(2)等价于
联立可得:
再根据之前解一元线性同余方程的方法,很容易得到这个方程的解,因此小于a的·非负整数解即为 %a 。
那么不管这样的方程有多少个,都可以两两解决,求得方程组的最终解。
模板代码:
int solve(){
cin>>a1>>r1;
for(int i=1;i<n;i++){
cin>>a2>>r2;
ll a=a1,b=a2,c=r2-r1;
exgcd(a,b,d,x0,y0);
if(c%d!=0)
{
r1=-1;
break;
}
ll t=b/d;
x0=(x0*(c/d)%t+t)%t;
r1=a1*x0+r1;
a1=a1*(a2/d);
}
return r1;
}
附上其他博主的具体证明:
解高次同余方程组:
模板代码:
/*
解高次同余方程
A^x ≡ B( mod C )
c 是素数
时间复杂度O(sqrt(n)*logn)
POJ 2471
*/
#include <cstdio>
#include <cstring>
#include <cmath>
#include <map>
#include <iostream>
#include <algorithm>
using namespace std;
#define LL long long
//快速幂求a^b
LL pow_mod(LL a,LL b,LL n)
{
LL s=1;
while(b)
{
if(b&1)
s=(s*a)%n;
a=(a*a)%n;
b=b>>1;
}
return s;
}
//求解模方程a^x=b(mod n)。n为素数,无解返回-1
//费马小定理a^(n-1)=1(mod n),n为素数。a^0=1,所以循环节小于等于n,即如果存在解,则最小解x<=n
LL log_mod (LL a,LL b,LL n)
{
LL m,v,e=1,i;
m=ceil(sqrt(n+0.5)); //x=i*m+j
//v=inv(pow_mod(a,m,n),n); //a^m*v=1(mod n)
v=pow_mod(a,n-m-1,n);
map<LL,LL>x;
x[1]=m;
for(i=1;i<m;i++) //建哈希表,保存x^0,x^1,...,x^m-1
{
e=(e*a)%n;
if(!x[e])x[e]=i;
}
for(i=0;i<m;i++)//每次增加m次方,遍历所有1<=x<=n
{
if(x[b])
{
LL num=x[b];
x.clear();//需要清空,不然会爆内存
return i*m+(num==m?0:num);
}
b=(b*v)%n; //b=b/(a^m)
}
return -1;
}
int main()
{
LL a,b,n;
while(scanf("%I64d%I64d%I64d",&n,&a,&b)!=EOF)
{
LL ans=log_mod(a,b,n);
if(ans==-1)printf("no solution\n");
else printf("%I64d\n",ans);
}
return 0;
}
中国剩余定理:https://blog.youkuaiyun.com/qq_41515833/article/details/86624537
求此类同余方程组最小负整数的模板:
ll China(ll r){
M=1;
ll i,Mi,x0,y0,d,ans=0;
for(int i=1;i<=r;i++){
M*=m[i];
}
for(int i=1;i<=r;i++){
Mi=M/m[i];
exgcd(Mi,m[i],d,x0,y0);
ans=(ans+Mi*x0*a[i])%M;
}
if(ans<0){
ans+=M;
}
return ans;
}
也可以用解线性同余方程组的方法求解:
ac代码如下:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll m[10],a[10],M;
void exgcd(ll a,ll b,ll &d,ll &x,ll &y){
if(b==0){
x=1;y=0;
d=a;
return ;
}
else{
exgcd(b,a%b,d,x,y);
ll temp=x;
x=y;
y=temp-(a/b)*y;
}
}
ll China(ll r){
M=1;
ll i,Mi,x0,y0,d,ans=0;
for(int i=1;i<=r;i++){
M*=m[i];
}
for(int i=1;i<=r;i++){
Mi=M/m[i];
exgcd(Mi,m[i],d,x0,y0);
ans=(ans+Mi*x0*a[i])%M;
}
if(ans<0){
ans+=M;
}
return ans;
}
int main(){
ll t=0,p,e,i,d,temp;
while(cin>>p>>e>>i>>d){
if(p==-1&&e==-1&&i==-1&&d==-1){
break;
}
t++;
a[1]=p,a[2]=e,a[3]=i;
m[1]=23,m[2]=28,m[3]=33;
ll ans=China(3);
while(ans<=d){
ans+=M;
}
cout<<"Case "<<t<<":the next triple peak occurs in "<<ans-d<<" days."<<endl;
}
return 0;
}