10 10 10除以 7 7 7余数是 3 3 3, 17 17 17除以 7 7 7余数也是 3 3 3,那么就称 10 10 10与 17 17 17在模 7 7 7意义下同余,符号表示为 10 ≡ 17 ( m o d 7 ) \;10\equiv 17 \; (mod \; 7) 10≡17(mod7)。
然后,很容易得到一些性质:
这些都显然,不重要,重要的在后面。
为了简单阅读,加减乘就证明了,直接贴代码。
基于一个原理,先加减乘再取模和先取模再加减乘,结果是不影响的,需读者自己思考想清楚。
模p意义下的 a + b a+b a+b:(ps:这里代码的计算答案均赋值给c)
c=(a+b)%p;
模p意义下的 a − b a-b a−b:
c=(a%p-b%p+p)%p;
这里需要注意的是,%对负数的处理是,先对其绝对值取模,再将符号乘给它,所以我们要保证它结果为正数,需取模 p p p后加上模数 p p p再取模。
模p意义下的 a × b a\times b a×b;
c=a*b%p;
不管是什么运算都要注意不要溢出,比如a和b均大于模数p,且相乘会溢出的话,应该改写乘c=(a%p)*(b%p)%p,这个需自行判断。
思考一个问题,先除再模和先模再除,结果是否一样?
答案显然是否,因为会有除不尽的情况。
所以引入了逆元。
什么是逆元?
通俗来讲, a a a的逆元就是 a − 1 a^{-1} a−1。
那么我们要除以 a a a,相当于只要乘上 a − 1 a^{-1} a−1即可。
当 a − 1 a^{-1} a−1是 a a a模 p p p意义下的逆元,那么就有 a a − 1 ≡ 1 ( m o d p ) aa^{-1}\equiv 1 \; (mod \; p) aa−1≡1(modp)
逆元不是一定存在的!!
模 p p p意义下, a a a的逆元存在的充要条件是 [ g c d ( a , p ) = 1 ] [gcd(a,p)=1] [gcd(a,p)=1]。
举个例子, 2 2 2乘上 w h a t what what在模 4 4 4意义下等于 1 1 1,不存在吧,这种情况就是没有逆元。
ps:准确来讲不应该是等于,而是与 1 1 1同余,当模数为 1 1 1时,任何数都与 1 1 1同余
那么现在考虑怎么求逆元:
先上费马小定理: a p − 1 ≡ 1 ( m o d p ) a^{p-1}\equiv 1 \; (mod \; p) ap−1≡1(modp)
成立条件: p p p为素数,且 [ g c d ( a , p ) = 1 ] [gcd(a,p)=1] [gcd(a,p)=1]
然后就有了: a − 1 ≡ a p − 2 ( m o d p ) a^{-1}\equiv a^{p-2} \; (mod \; p) a−1≡ap−2(modp)
所以我们现在只要求 a p − 2 a^{p-2} ap−2就好了(前提要满足费马小定理),上模板:
//才疏学浅,不敢用位运算
ll fpow(ll a,ll n,ll mod)//快速幂
{
ll sum=1,base=a%mod;
while(n!=0)
{
if(n%2)sum=sum*base%mod;
base=base*base%mod;
n/=2;
}
return sum;
}
ll inv(ll a,ll mod)//逆元
{
return fpow(a,mod-2,mod);
}
那么现在要研究不满足费马小定理的逆元求法。
当不满足 [ g c d ( a , p ) = 1 ] \;[gcd(a,p)=1]\; [gcd(a,p)=1]那么逆元不存在,所以我们只考虑 p p p不是素数。
要求方程 a x ≡ 1 ( m o d p ) \;ax\equiv 1 \; (mod \; p) ax≡1(modp)的解。
等效成求 a x + p y = 1 \;ax+py=1 ax+py=1的解,然后就有拓展欧几里得求解。
传送门
学完了加减乘除,就可以轻松实战一下了。
求解方程: a x − b ≡ c ( m o d p ) ax-b\equiv c \; (mod \; p) ax−b≡c(modp)
那么只要两边同时加上 b b b,再同乘 a − 1 a^{-1} a−1即可。
就得到了: x ≡ a − 1 ( b + c ) ( m o d p ) x\equiv a^{-1}(b+c) \; (mod \; p) x≡a−1(b+c)(modp)
是不是很快就上手了。
先来一个场景,模数 p = 1 e 9 + 7 p=1e9+7 p=1e9+7,要算 a × b ( m o d p ) a\times b(mod\; p) a×b(modp),那么取模后的 a a a和 b b b都不会大于 1 e 9 + 7 1e9+7 1e9+7,两个乘起来也不会爆long long,这时候开long long 就可以了。
那么如果模数 p = 1 e 18 + 7 p=1e18+7 p=1e18+7,那么取模后相乘仍有爆long long的危险,这时候要怎么办,这里介绍两种办法。
一种是江湖邪术:__int128
顾名思义,就是128位存储的整数,大到 2 127 − 1 2^{127}-1 2127−1。
不过它只能在Linux环境下能够使用,在大部分oj上都能用,本地编译器无法运行,可以用long long 过了样例再换掉提交。
它自带了各种基本运算,以及强制转换符号,但是没有特定的读入和输出,所以可以用long long读进来后,强制转化成(__int128)再进行运算。
或者手写一个读入和输出,原理就是用getchar()和putchar()以及char类型和__int128型的强制转换。
__int128读入读出模板:
ll read()
{
int X=0,w=0; char ch=0;
while(!isdigit(ch)) {w|=ch=='-';ch=getchar();}
while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
return w?-X:X;
}
void print(ll x)
{
if(x<0){putchar('-');x=-x;}
if(x>9) print(x/10);
putchar(x%10+'0');
}
同样可以适用于long long的读入,使用时只要把typedef long long ll;改成typedef __int128 ll;即可。
第二种办法就是快速乘:
原理和快速幂一样,只是乘法运算变成了加法运算,复杂度是log。
直接上o(log)的模板:
ll fmul(ll a,ll b,ll mod)
{
ll sum=0,base=(a%mod+mod)%mod;
while(b)
{
if(b%2)sum=(sum+base)%mod;
base=(base+base)%mod;
b/=2;
}
return sum;
}
还有一种o(1)的写法,因为C++内有128位的long double型,可以利用它优化成o(1)。
o(1)的快速乘模板:
ll fmul(ll x,ll y,ll mod)
{
ll tmp=(x*y-(ll)((long double)x/mod*y+1.0e-8)*mod);
return tmp<0?tmp+mod:tmp;
}
注意,o(1)快速乘因为原理是利用128位的long double,所以将__int128和其混用,并不能改善爆__int128的问题。
但是用o(log)的快速乘和__int128混用却可以解决模数大至__int128的乘法问题。
什么是剩余
多出来的;遗留下来的:剩余物资。出自《现代汉语词典》
剩余就是某个物体分成若干份等量或不等量的物体,使用后还有没使用的,没使用的对于已使用过的就算是剩余。
总之就是余数。
剩余类: 设模为n,则根据余数可将所有的整数分为n类,把所有与整数a模n同余的整数构成的集合叫做模n的一个剩余类
剩余系: 对于特定的 n n n,一个整数集合里所有数对其取模后剩下来的集合。
完全剩余系: 从模n的每个剩余类中各取一个数,得到一个由n个数组成的集合,叫做模n的一个完全剩余系。差不多就是 0 0 0到 n − 1 n-1 n−1。
什么是二次剩余
对于二次同余方程 x 2 ≡ n ( m o d p ) \;x^{2}\equiv n \; (mod \; p) x2≡n(modp),若 [ g c d ( n , p ) = 1 ] [gcd(n,p)=1] [gcd(n,p)=1],且存在一个 x x x满足该方程,则称 n n n是模 p p p意义下的二次剩余,若无解,则称n为p的二次非剩余。
欧拉判别法:(书上还写了一个高斯引理,可以判定与p互素的整数是否是二次剩余,看不进去了)
对于 p p p是奇素数,我们有办法来判断 n n n是否为 p p p的二次剩余。
我们假设存在解 x 0 x_{0} x0使得 x 0 2 ≡ n ( m o d p ) \;x_{0}^{2}\equiv n \; (mod \; p) x02≡n(modp)。
式子转化后变成 x 0 p − 1 ≡ n p − 1 2 ( m o d p ) x_{0}^{p-1}\equiv n^{\frac{p-1}{2}} (mod\; p) x0p−1≡n2p−1(modp)。
根据费马小定理,就有了 n p − 1 2 ≡ 1 ( m o d p ) n^{\frac{p-1}{2}} \equiv 1 (mod\; p) n2p−1≡1(modp),此时方程有解。
且 ( n p − 1 2 − 1 ) ( n p − 1 2 + 1 ) ≡ 0 ( m o d p ) (n^{\frac{p-1}{2}}-1)(n^{\frac{p-1}{2}}+1) \equiv 0 (mod\; p) (n2p−1−1)(n2p−1+1)≡0(modp),得到 n p − 1 2 ≡ ± 1 ( m o d p ) n^{\frac{p-1}{2}} \equiv \pm 1 (mod\; p) n2p−1≡±1(modp)。
另外只需要证明等于 − 1 -1 −1时无解即可,此处证明略。
我们引入勒让德符号 ( n p ) ≡ n p − 1 2 ( m o d p ) (\frac{n}{p}) \equiv n^{\frac{p-1}{2}} (mod\; p) (pn)≡n2p−1(modp),其中 n n n不能被 p p p整除,整除时答案即为 0 0 0。
有个结论,对于 p p p为奇素数,若 ( n p ) = 1 (\frac{n}{p})=1 (pn)=1,则 n n n是 p p p的二次剩余,反之若 ( n p ) = − 1 (\frac{n}{p})=-1 (pn)=−1,则 n n n是 p p p的非二次剩余。
还有三个定理:
定理自行证明。
还有一个更流批 一点的定理,对于奇素数 p p p,方程 x 2 ≡ n ( m o d p ) \;x^{2}\equiv n(mod\; p) x2≡n(modp),共有 p − 1 2 + 1 \frac{p-1}{2}+1 2p−1+1个 n n n使得方程有解,换句话说有一半的二次剩余。
现在问题来了!!!传送门
如何求解方程 x 2 ≡ n ( m o d p ) \;x^{2}\equiv n(mod\; p) x2≡n(modp),其中 p p p为奇素数。
偶素数其实就一个 2 2 2嘛,特判一下就好了,就可以知道 p p p是素数的了。
这显然可以用BSGS算法来求解。
我们考虑比 o ( p ) o(\sqrt{p}) o(p)快的做法,但是仅限于 p p p为奇素数的情况,如果没有这个条件,还是老老实实地BSGS吧。
考虑 o ( l o g ) o(log) o(log)的做法:Cipolla算法
首先用欧拉判别法判断是否有解, n = 0 n=0 n=0的情况特殊考虑,没解就写个无解然后下一题。
然后有非 0 0 0解的话,则一定是两个解,互为相反数,即 x 0 x_{0} x0和 p − x 0 p-x_{0} p−x0。
我们先随机找一个 a a a,使得 a 2 − n a^{2}-n a2−n是 p p p的非二次剩余,因为非二次剩余占一半,所以期望大约两次就可以找到了。
于是乎, x ≡ ( a + a 2 − n ) p + 1 2 ( m o d p ) x\equiv (a+\sqrt{a^{2}-n})^{\frac{p+1}{2}}(mod\; p) x≡(a+a2−n)2p+1(modp)就是方程的解了!!
????????!
好吧我们来证明一下。
我们令 i = a 2 − n i=\sqrt{a^{2}-n} i=a2−n,因为 a 2 − n a^{2}-n a2−n是二次非剩余,所以 i i i的值是不存在的,我们将数域扩张,引入类似复数的东西。
现在我们有了下面的东西,
第一个根据定义。第二个是利用二次非剩余的欧拉判别性质。第三个是用二项式定理展开后,中间项都模 p p p消除了。
然后开始操作起来:
( a + i ) p + 1 ≡ ( a p + i p ) ( a + i ) ≡ ( a − i ) ( a + i ) ≡ a 2 − i 2 ≡ n (a+i)^{p+1}\equiv(a^{p}+i^{p})(a+i)\equiv(a-i)(a+i)\equiv a^{2}-i^{2}\equiv n (a+i)p+1≡(ap+ip)(a+i)≡(a−i)(a+i)≡a2−i2≡n
所以 ( a + i ) p + 1 2 ≡ n 1 2 (a+i)^{\frac{p+1}{2}}\equiv n^{\frac{1}{2}} (a+i)2p+1≡n21。
并且可以自证左边展开后是一个实数,否则若仍有虚部,则无法解决问题。
就证好了。
然后只要写一个复数类,再套快速幂就可以了。
上代码:
#include
using namespace std;
typedef long long ll;
typedef struct cp
{
ll r,i;
cp(const int&x,const int &y){r=x,i=y;}
}cp;
cp mul(cp a,cp b,ll p,ll w)
{
return cp((a.r*b.r%p+a.i*b.i%p*w%p)%p,(a.r*b.i%p+a.i*b.r%p)%p);
}
cp cp_fpow(cp a,ll n,ll p,ll w)
{
cp sum(1,0),base=a;
while(n!=0)
{
if(n%2)sum=mul(sum,base,p,w);
base=mul(base,base,p,w);
n/=2;
}
return sum;
}
ll fpow(ll a,ll n,ll p)
{
ll sum=1,base=a%p;
while(n!=0)
{
if(n%2)sum=sum*base%p;
base=base*base%p;
n/=2;
}
return sum;
}
bool OulaPb(ll n,ll p)//欧拉判别
{
if(fpow(n,(p-1)/2,p)==p-1)return false;
else return true;
}
ll Cipolla(ll n,ll p)//无解返回-1
{
n=(n%p+p)%p;
if(n==0)return 0;
if(p==2&&n==1)return 1;
if(!OulaPb(n,p))return -1;
ll a=rand()%p*rand()%p;
while(OulaPb((a*a%p-n%p+p)%p,p))a=rand()%p*rand()%p;
cp ans=cp_fpow(cp(a,1),(p+1)/2,p,(a*a%p-n%p+p)%p);
return ans.r%p;
}
int main()
{
srand(time(0));
ll T;
scanf("%lld",&T);
while(T--)
{
ll n,p;
scanf("%lld%lld",&n,&p);
ll ans=Cipolla(n,p);
if(ans==-1)printf("Hola!\n");
else if(ans==(p-ans)%p)printf("%lld\n",ans);
else printf("%lld %lld\n",min(ans,p-ans),max(ans,p-ans));
}
return 0;
}
关于 p p p不为素数的,书上有介绍一种方法,将模数因子分解,成若干个奇素数,求出若干个方程的解,然后用中国剩余定理合并。
比如 x 2 ≡ 860 ( m o d 11021 ) x^{2}\equiv 860(mod \;11021) x2≡860(mod11021)。
其中 11021 = 103 × 107 11021=103\times 107 11021=103×107。
根据 a ( m o d d ) = a ( m o d n ) ( m o d d ) a(mod\;d)=a(mod\; n)(mod \;d) a(modd)=a(modn)(modd)对 [ d ∣ n ] [d|n] [d∣n]成立。
可以得到 x 2 ≡ 860 ≡ 36 ( m o d 103 ) x^{2}\equiv 860\equiv 36(mod\; 103) x2≡860≡36(mod103),和 x 2 ≡ 860 ≡ 4 ( m o d 107 ) x^{2}\equiv 860\equiv 4(mod\; 107) x2≡860≡4(mod107)。
每个方程分别有两个解,然后用中国剩余定理合并,得到四个解。
具体操作就不说了。