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

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

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) 1017(mod7)
 
然后,很容易得到一些性质:

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

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

同余意义下的加减乘:

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

c=(a+b)%p;

 
模p意义下的 a − b a-b ab

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} a1
那么我们要除以 a a a,相当于只要乘上 a − 1 a^{-1} a1即可。
a − 1 a^{-1} a1 a a a p p p意义下的逆元,那么就有 a a − 1 ≡ 1    ( m o d    p ) aa^{-1}\equiv 1 \; (mod \; p) aa11(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) ap11(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) a1ap2(modp)
 
所以我们现在只要求 a p − 2 a^{p-2} ap2就好了(前提要满足费马小定理),上模板:

//才疏学浅,不敢用位运算
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) ax1(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) axbc(modp)
那么只要两边同时加上 b b b,再同乘 a − 1 a^{-1} a1即可。
就得到了: x ≡ a − 1 ( b + c )    ( m o d    p ) x\equiv a^{-1}(b+c) \; (mod \; p) xa1(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 21271
不过它只能在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 n1

什么是二次剩余
对于二次同余方程    x 2 ≡ n    ( m o d    p ) \;x^{2}\equiv n \; (mod \; p) x2n(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) x02n(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) x0p1n2p1(modp)
根据费马小定理,就有了 n p − 1 2 ≡ 1 ( m o d    p ) n^{\frac{p-1}{2}} \equiv 1 (mod\; p) n2p11(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) (n2p11)(n2p1+1)0(modp),得到 n p − 1 2 ≡ ± 1 ( m o d    p ) n^{\frac{p-1}{2}} \equiv \pm 1 (mod\; p) n2p1±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)n2p1(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的非二次剩余。
还有三个定理:

  • a ≡ b ( m o d    p ) a \equiv b(mod\;p) ab(modp),则 ( a p ) = ( b p ) (\frac{a}{p})=(\frac{b}{p}) (pa)=(pb)
  • ( a p ) ( b p ) = ( a b p ) (\frac{a}{p})(\frac{b}{p})=(\frac{ab}{p}) (pa)(pb)=(pab)
  • ( a 2 p ) = 1 (\frac{a^{2}}{p})=1 (pa2)=1

定理自行证明。
 
还有一个更流批 一点的定理,对于奇素数 p p p,方程    x 2 ≡ n ( m o d    p ) \;x^{2}\equiv n(mod\; p) x2n(modp),共有 p − 1 2 + 1 \frac{p-1}{2}+1 2p1+1 n n n使得方程有解,换句话说有一半的二次剩余。
 
 
现在问题来了!!!传送门
如何求解方程    x 2 ≡ n ( m o d    p ) \;x^{2}\equiv n(mod\; p) x2n(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} px0
我们先随机找一个 a a a,使得 a 2 − n a^{2}-n a2n 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+a2n )2p+1(modp)就是方程的解了!!
????????!
好吧我们来证明一下。
我们令 i = a 2 − n i=\sqrt{a^{2}-n} i=a2n ,因为 a 2 − n a^{2}-n a2n是二次非剩余,所以 i i i的值是不存在的,我们将数域扩张,引入类似复数的东西。
现在我们有了下面的东西,

  • i 2 ≡ a 2 − n    ( m o d    p ) i^{2}\equiv a^{2}-n\;(mod\;p) i2a2n(modp)
  • i p − 1 ≡ − 1    ( m o d    p ) i^{p-1}\equiv -1\;(mod\;p) ip11(modp)(这条好像没用到)
  • ( A + B ) p ≡ A p + B p    ( m o d    p ) (A+B)^{p}\equiv A^{p}+B^{p}\;(mod\;p) (A+B)pAp+Bp(modp)
  • ( x 1 + y 1 i ) ( x 2 + y 2 i ) = x 1 x 2 + y 1 y 2 ( a 2 − n ) + ( x 1 y 2 + x 2 y 1 ) i (x_{1}+y_{1}i)(x_{2}+y_{2}i)=x_{1}x_{2}+y_{1}y_{2}(a^{2}-n)+(x_{1}y_{2}+x_{2}y_{1})i (x1+y1i)(x2+y2i)=x1x2+y1y2(a2n)+(x1y2+x2y1)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)(ai)(a+i)a2i2n
所以 ( a + i ) p + 1 2 ≡ n 1 2 (a+i)^{\frac{p+1}{2}}\equiv n^{\frac{1}{2}} (a+i)2p+1n21
并且可以自证左边展开后是一个实数,否则若仍有虚部,则无法解决问题。
就证好了。
然后只要写一个复数类,再套快速幂就可以了。
 
上代码:

#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) x2860(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] [dn]成立。
可以得到 x 2 ≡ 860 ≡ 36 ( m o d    103 ) x^{2}\equiv 860\equiv 36(mod\; 103) x286036(mod103),和 x 2 ≡ 860 ≡ 4 ( m o d    107 ) x^{2}\equiv 860\equiv 4(mod\; 107) x28604(mod107)
每个方程分别有两个解,然后用中国剩余定理合并,得到四个解。
具体操作就不说了。
 
 
 
 
 

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

  • 威尔逊定理:p可整除(p-1)!+1是p为质数的充要条件。
  • 欧拉定理:若    g c d ( a , n ) = 1 \;gcd(a,n)=1 gcd(a,n)=1,则 a φ ( n ) ≡ 1 ( m o d    n ) a^{\varphi (n)}\equiv 1(mod \; n) aφ(n)1(modn),其中 φ ( n ) \varphi (n) φ(n)为欧拉函数。
  • 孙子定理:用来求解模数两两互质的同余方程组,传送门
  • 费马小定理:若 p p p为素数,且 g c d ( a , p ) = 1 gcd(a,p)=1 gcd(a,p)=1,则 a p − 1 ≡ 1 ( m o d    p ) a^{p-1}\equiv 1(mod \; p) ap11(modp)。(其实 p p p为素数时, φ ( n ) = p − 1 \varphi (n)=p-1 φ(n)=p1)。

你可能感兴趣的:(数论)