【笔记】数论 (同余)
Exgcd(Extend Gcd):
假设我们都知道欧几里得算法,那个传说中的辗转相除法。
引入:求解方程\(ax+by=c\),\(a,b\)是整数。
首先,咱们需要一个定理:
Bézout 定理: \(ax+by=gcd(a,b)\)是保证有解的 。
证明(巨佬:显然:
因为\(gcd(a,b)\)=\(gcd(b\),\(a\) \(mod\) \(b)\)
考虑到当\(b=0,a=gcd(a,b)\),那么一定有解\(x=1,y=0\)
于是我们只要证明当前\(x,y\)从\(gcd(b,\)\(a\) \(mod\) \(b)\)的x,y可以推导出来就保证有解了。
那么带入:
\(bx'+\) ( \(a\) \(mod\) \(b\)) \(y'=gcd(b,\)\(a\) \(mod\) \(b\))
\(bx'+(a-a/b*b)y'=gcd(b,\)\(a\) \(mod\) \(b\))
随便移项就可以得到:
(为什么随便移?答:写起来麻烦,手推下就晓得了。\(ay'+b(x'-a/b*y')=gcd(b,\)\(a\) \(mod\) \(b\))\(=gcd(a,b)\)
那么\(x=y',y=(x'-a/b*y')\)
证明结束之后发现我们发现了一种求出\(ax+by=gcd(a,b)\)的x,y特解的快速办法!
那么对于\(ax+by=c\)只要先求解:\(ax+by=gcd(a,b)\)然后再把求出来的解乘上\(\frac{c}{gcd(a,b)}\)
求完一组特解。
那么通解就是:(设\(x_0,y_0\)为gcd那个式子的解
\(x=\frac{c}{gcd(a,b)}x_0+k\frac{b}{gcd(a,b)}\)
\(y=\frac{c}{gcd(a,b)}y_0-k\frac{a}{gcd(a,b)}\)
那么咱们考虑咋么用介个求解线性同余方程捏~~
先列一个方程:
\(ax \equiv b \pmod{p}\)
发现其实这方程意思是:\(ax-b\)是p的倍数,那么为什么不为了方便设成-y倍呢。
那么不就变成方程了吗:
\(ax+py=b\) 在求一遍Exgcd就解出x了~~
代码:
inline int exgcd(int a,int b,int &x,int &y){
if(!b) { x=1;y=0;return a;}
int tmp=exgcd(b,a%b,x,y);
int z=x;x=y;y=z-a/b*y;
return tmp;
}
CRT(Chinese Remainder Theorom):
其实这玩意好像超强,但他实际也超强!
求同余方程的最小解(要求m互质。
\(x \equiv a_1 \pmod {m_1}\)
\(x \equiv a_2 \pmod {m_2}\)
\(x \equiv a_3 \pmod {m_3}\)
......
\(x \equiv a_k \pmod {m_k}\)
那么,打个公式吧:
设\(M=\prod_{i=1}^{k}m_i\)
并且\(t_i \frac{M}{m_i} \equiv 1 \pmod {m_i}\)
那么\(x=\sum_{i=1}^{k}t_ia_i\frac{M}{m_i}\)
其实也很好理解啊
注意龟速乘
int china(){
int ans=0,lcm=1,x,y;
for(int i=1;i<=k;++i) lcm*=b[i];
for(int i=1;i<=k;++i){
int tp=lcm/b[i];
exgcd(tp,b[i],x,y);
x=(x%b[i]+b[i])%b[i];
ans=(ans+tp*x*a[i])%lcm;
}
return (ans+lcm)%lcm;
}
ExCRT(Extend Chinese Remainder Theorom)
//M为前k个模数的最小公约数
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,M,ans,x,y;
const int N=1e5;
int a[N+5],b[N+5];
inline int mul(int a,int b,int p){
int ret=0;
while(b>0){
if(b&1) ret=(ret+a)%p;
a=(a+a)%p;
b>>=1;
}
return ret;
}
inline int Exgcd(int a,int b,int &x,int &y){
if(b==0) { x=1;y=0;return a;}
int tmp=Exgcd(b,a%b,x,y);
int z=x;x=y;y=z-a/b*y;
return tmp;
}
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;++i)
scanf("%lld",&b[i]),scanf("%lld",&a[i]);
for(int i=1;i<=n;++i) a[i]=(a[i]%b[i]+b[i])%b[i];
ans=a[1];M=b[1];
//优先处理第一个式子的通解
for(int i=2;i<=n;++i){
int oo=((a[i]-ans)%b[i]+b[i])%b[i];
int p=Exgcd(M,b[i],x,y);
//求解方程x+t*M与ai同余的t的最小值
if(oo%p!=0) return puts("-1"),0;//无解
x=mul(x,oo/p,b[i]/p);//我记得在青蛙那篇博客里写到了求最小解要%...
ans+=x*M;//注意这几句的顺序,这里乘的是前k-1个的lcm:M
M*=(b[i]/p);
ans=(ans%M+M)%M;
}
ans=(ans%M+M)%M;
printf("%lld\n",ans);
return 0;
}
欧拉定理(Euler ’ theorom) | 扩展欧拉定理(Extend Euler ’ theorom)
这个鬼东西其实挺好懂的,证明略(只是我懒,才不是我不会呢!
哼哼,事实证明我是不会证明。。。
但这并不重要!
欧拉定理:
\(a^{\phi (p)} \equiv 1 \pmod {p}\)
扩展:
\(a^b\equiv\) \(a^{b \mod\phi(p)}\pmod{p}\) \((gcd(a,p)=1)\)
\(a^b\equiv\) \(a^{b \mod\phi(p)+\phi(p)} \pmod{p}\) \((gcd(a,p)>1)\)
证明:(第一个显然,第二个不会。。。)
BSGS(Baby Step Giant Step):
坑放在这里等着明天填。。。(一不小心香了。。。)
我来填坑了~~
BSGS多用于求解高次同余方程(但普通BSGS只能解决p为质数的情况)
高次同余方程形如:
\(a^x \equiv b\pmod {p}\)
给出a,求解x。
BSGS复杂度\(O(\sqrt {p})\)
流程:
首先我们把x表示成
\(i*t-j\),\(t=(int)\sqrt{p}+1\)
那么我们把方程变为:
\(a^{i*t-j} \equiv b \pmod p\)
再来:
\(a^{i*t} \equiv b*a^{j}\pmod p\)
\((a^{t})^i\equiv b*a^j \pmod p\)
我们可以先把所有\(b*a^j\)的值插入hash
之后我们可以枚举i,然后hash中查找是否有符合条件的j,
j枚举是要小于t的,不然会发现式子变成了:\((a^t)^i \equiv b*a^t \pmod {p}\)
不知道为什么,这个式子看起来不太对,我也不知道为什么不能。。。(逃
i可以枚举到t。
现在看了那么多流程来证明一下为什么把x分解成\(i*t-j\)
根据欧拉定理推论:
\(a^b\equiv a^{b \% \phi(p)}\pmod p(gcd(a,p=1))\)
当p是个质数就把\(\phi(p)\)变成\(p-1\)
那么x要小于p-1,不然就可以变成小于等于p的x,可以模掉。
那么x只要枚举到p就好了~~
所以就是\(i*t-j<=p\),那么\(t,i,j\)最大可以取\(\sqrt{p}\)
这样就可以
代码:(代码用了拉链hash)
inline void BSGS(int a,int b,int p){
H.clear();
if(a%p==0) { puts("Orz, I cannot find x!");return;}
b%=p;
int t=(int)sqrt(p)+1;
for(int j=0;j<t;++j){
int val=1ll*(b*power(a,j,p)%p)%p;
H.insert(val,j);
}//枚举j的取值并存入hash
a=power(a,t,p)%p;//先求出a^t的值
for(int i=0;i<=t;++i){
int val=power(a,i,p)%p;
int j=H.locate(val);
if(j==-1) continue;
if(i*t-j>=0) { printf("%lld\n",i*t-j);return;}
}
puts("Orz, I cannot find x!");
}