同余意义下的运算法则与逆元、和二次剩余、和数论四大定理

让人想犯罪 __ 提交于 2020-01-27 00:29:30

同余:(这里只讲整数的同余)

1010除以77余数是331717除以77余数也是33,那么就称10101717在模77意义下同余,符号表示为  1017  (mod  7)\;10\equiv 17 \; (mod \; 7)
 
然后,很容易得到一些性质:

  • 自反性:aa  (mod  m)a\equiv a \; (mod \; m)
  • 对称性:若  ab  (mod  m)\;a\equiv b \; (mod \; m),则  ba  (mod  m)\;b\equiv a \; (mod \; m)
  • 传递性:若  ab  (mod  m)\;a\equiv b \; (mod \; m),且  bc  (mod  m)\;b\equiv c \; (mod \; m),则  ac  (mod  m)\;a\equiv c \; (mod \; m)

这些都显然,不重要,重要的在后面。
 
 

同余意义下的加减乘:

为了简单阅读,加减乘就证明了,直接贴代码。
基于一个原理,先加减乘再取模和先取模再加减乘,结果是不影响的,需读者自己思考想清楚。
 
模p意义下的a+ba+b:(ps:这里代码的计算答案均赋值给c)

c=(a+b)%p;

 
模p意义下的aba-b

c=(a%p-b%p+p)%p;

这里需要注意的是,%对负数的处理是,先对其绝对值取模,再将符号乘给它,所以我们要保证它结果为正数,需取模pp后加上模数pp再取模。
 
模p意义下的a×ba\times b;

c=a*b%p;

不管是什么运算都要注意不要溢出,比如a和b均大于模数p,且相乘会溢出的话,应该改写乘c=(a%p)*(b%p)%p,这个需自行判断。

 
 
 

模意义下的除法:

思考一个问题,先除再模和先模再除,结果是否一样?
答案显然是否,因为会有除不尽的情况。
所以引入了逆元
 
什么是逆元?
通俗来讲,aa的逆元就是a1a^{-1}
那么我们要除以aa,相当于只要乘上a1a^{-1}即可。
a1a^{-1}aapp意义下的逆元,那么就有aa11  (mod  p)aa^{-1}\equiv 1 \; (mod \; p)
 
逆元不是一定存在的!!
pp意义下,aa的逆元存在的充要条件是[gcd(a,p)=1][gcd(a,p)=1]
举个例子,22乘上whatwhat在模44意义下等于11,不存在吧,这种情况就是没有逆元。
ps:准确来讲不应该是等于,而是与11同余,当模数为11时,任何数都与11同余
 
那么现在考虑怎么求逆元:
先上费马小定理:ap11  (mod  p)a^{p-1}\equiv 1 \; (mod \; p)
成立条件:pp为素数,且[gcd(a,p)=1][gcd(a,p)=1]
然后就有了:a1ap2  (mod  p)a^{-1}\equiv a^{p-2} \; (mod \; p)
 
所以我们现在只要求ap2a^{p-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);
}

 
 
那么现在要研究不满足费马小定理的逆元求法。
当不满足  [gcd(a,p)=1]  \;[gcd(a,p)=1]\;那么逆元不存在,所以我们只考虑pp不是素数。
要求方程  ax1  (mod  p)\;ax\equiv 1 \; (mod \; p)的解。
等效成求  ax+py=1\;ax+py=1的解,然后就有拓展欧几里得求解。
传送门

 
 
学完了加减乘除,就可以轻松实战一下了。
求解方程:axbc  (mod  p)ax-b\equiv c \; (mod \; p)
那么只要两边同时加上bb,再同乘a1a^{-1}即可。
就得到了:xa1(b+c)  (mod  p)x\equiv a^{-1}(b+c) \; (mod \; p)
是不是很快就上手了。
 
 

再介绍一下快速乘:

先来一个场景,模数p=1e9+7p=1e9+7,要算a×b(mod  p)a\times b(mod\; p),那么取模后的aabb都不会大于1e9+71e9+7,两个乘起来也不会爆long long,这时候开long long 就可以了。
那么如果模数p=1e18+7p=1e18+7,那么取模后相乘仍有爆long long的危险,这时候要怎么办,这里介绍两种办法。
 
一种是江湖邪术:__int128
顾名思义,就是128位存储的整数,大到212712^{127}-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的一个剩余类
剩余系: 对于特定的nn,一个整数集合里所有数对其取模后剩下来的集合。
完全剩余系: 从模n的每个剩余类中各取一个数,得到一个由n个数组成的集合,叫做模n的一个完全剩余系。差不多就是00n1n-1

什么是二次剩余
对于二次同余方程  x2n  (mod  p)\;x^{2}\equiv n \; (mod \; p),若[gcd(n,p)=1][gcd(n,p)=1],且存在一个xx满足该方程,则称nn是模pp意义下的二次剩余,若无解,则称n为p的二次非剩余。

 
欧拉判别法:(书上还写了一个高斯引理,可以判定与p互素的整数是否是二次剩余,看不进去了)
对于pp奇素数,我们有办法来判断nn是否为pp的二次剩余。
我们假设存在解x0x_{0}使得  x02n  (mod  p)\;x_{0}^{2}\equiv n \; (mod \; p)
式子转化后变成x0p1np12(mod  p)x_{0}^{p-1}\equiv n^{\frac{p-1}{2}} (mod\; p)
根据费马小定理,就有了np121(mod  p)n^{\frac{p-1}{2}} \equiv 1 (mod\; p),此时方程有解。
(np121)(np12+1)0(mod  p)(n^{\frac{p-1}{2}}-1)(n^{\frac{p-1}{2}}+1) \equiv 0 (mod\; p),得到np12±1(mod  p)n^{\frac{p-1}{2}} \equiv \pm 1 (mod\; p)
另外只需要证明等于1-1时无解即可,此处证明略。
 
我们引入勒让德符号(np)np12(mod  p)(\frac{n}{p}) \equiv n^{\frac{p-1}{2}} (mod\; p),其中nn不能被pp整除,整除时答案即为00
有个结论,对于pp为奇素数,若(np)=1(\frac{n}{p})=1,则nnpp的二次剩余,反之若(np)=1(\frac{n}{p})=-1,则nnpp的非二次剩余。
还有三个定理:

  • ab(mod  p)a \equiv b(mod\;p),则(ap)=(bp)(\frac{a}{p})=(\frac{b}{p})
  • (ap)(bp)=(abp)(\frac{a}{p})(\frac{b}{p})=(\frac{ab}{p})
  • (a2p)=1(\frac{a^{2}}{p})=1

定理自行证明。
 
还有一个更流批 一点的定理,对于奇素数pp,方程  x2n(mod  p)\;x^{2}\equiv n(mod\; p),共有p12+1\frac{p-1}{2}+1nn使得方程有解,换句话说有一半的二次剩余。
 
 
现在问题来了!!!传送门
如何求解方程  x2n(mod  p)\;x^{2}\equiv n(mod\; p),其中pp为奇素数。
偶素数其实就一个22嘛,特判一下就好了,就可以知道pp是素数的了。
这显然可以用BSGS算法来求解。
我们考虑比o(p)o(\sqrt{p})快的做法,但是仅限于pp为奇素数的情况,如果没有这个条件,还是老老实实地BSGS吧。
 
考虑o(log)o(log)的做法:Cipolla算法
首先用欧拉判别法判断是否有解,n=0n=0的情况特殊考虑,没解就写个无解然后下一题。
然后有非00解的话,则一定是两个解,互为相反数,即x0x_{0}px0p-x_{0}
我们先随机找一个aa,使得a2na^{2}-npp的非二次剩余,因为非二次剩余占一半,所以期望大约两次就可以找到了。
于是乎,x(a+a2n)p+12(mod  p)x\equiv (a+\sqrt{a^{2}-n})^{\frac{p+1}{2}}(mod\; p)就是方程的解了!!
????????!
好吧我们来证明一下。
我们令i=a2ni=\sqrt{a^{2}-n},因为a2na^{2}-n是二次非剩余,所以ii的值是不存在的,我们将数域扩张,引入类似复数的东西。
现在我们有了下面的东西,

  • i2a2ni^{2}\equiv a^{2}-n
  • ip11i^{p-1}\equiv -1(这条好像没用到)
  • (A+B)pAp+Bp(A+B)^{p}\equiv A^{p}+B^{p}

第一个根据定义。第二个是利用二次非剩余的欧拉判别性质。第三个是用二项式定理展开后,中间项都模pp消除了。
然后开始操作起来:
(a+i)p+1(ap+ip)(a+i)(ai)(a+i)a2i2n(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+12n12(a+i)^{\frac{p+1}{2}}\equiv n^{\frac{1}{2}}
并且可以自证左边展开后是一个实数,否则若仍有虚部,则无法解决问题。
就证好了。
然后只要写一个复数类,再套快速幂就可以了。
 
上代码:

#include <bits/stdc++.h>
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;
}

 
关于pp不为素数的,书上有介绍一种方法,将模数因子分解,成若干个奇素数,求出若干个方程的解,然后用中国剩余定理合并。
比如x2860(mod  11021)x^{2}\equiv 860(mod \;11021)
其中11021=103×10711021=103\times 107
根据a(mod  d)=a(mod  n)(mod  d)a(mod\;d)=a(mod\; n)(mod \;d)[dn][d|n]成立。
可以得到x286036(mod  103)x^{2}\equiv 860\equiv 36(mod\; 103),和x28604(mod  107)x^{2}\equiv 860\equiv 4(mod\; 107)
每个方程分别有两个解,然后用中国剩余定理合并,得到四个解。
具体操作就不说了。
 
 
 
 
 

最后再扔一个数论四大定理:

  • 威尔逊定理:p可整除(p-1)!+1是p为质数的充要条件。
  • 欧拉定理:若  gcd(a,n)=1\;gcd(a,n)=1,则aφ(n)1(mod  n)a^{\varphi (n)}\equiv 1(mod \; n),其中φ(n)\varphi (n)为欧拉函数。
  • 孙子定理:用来求解模数两两互质的同余方程组,传送门
  • 费马小定理:若pp为素数,且gcd(a,p)=1gcd(a,p)=1,则ap11(mod  p)a^{p-1}\equiv 1(mod \; p)。(其实pp为素数时,φ(n)=p1\varphi (n)=p-1)。
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!