cout << "Hello World!" << endl;|

xzm111

园龄:2年粉丝:1关注:0

二次剩余

二次剩余

若方程 xka(modm) 有解,则称 a 是模 mk 次剩余,否则称 a 是模 mk 次非剩余。下面只讨论 k=2 的情况,即二次剩余(平方剩余)。

先提出几个关键的定理(下面的 p 均为奇质数):

定理 1:若 a 是模 pk 次剩余,b 是模 pk 次非剩余,则 ab 是模 pk 次非剩余。

证明:若 ab 是模 pk 次剩余,可以设 x1ka(modp),x2kab(modp),于是 x2kx1kb(modp)

  • x10(modp),则 ab0(modp),与 abk 次非剩余矛盾。

  • x10(modp),则 (x2x11)kb(modp)p 为奇质数,x11 一定存在)。这与 bk 次非剩余矛盾。

定理 2:在模 p简化剩余系中,二次剩余和二次非剩余各占 p12

证明:设 px(简化剩余系),于是 x 可以写成 pq+r,其中 q,r 为整数,且 |r|p12

于是得出 x2=(pq+r)2r2(modp)

发现 1s<tp12:t2s2=(ts)(t+s),其中 1t+s,t+sp1,于是 (ts)0(modp),(t+s)0(modp),由于 p 为奇质数,于是 (ts)(t+s)0(modp)

1s<tp12:t2s20(modm)

这说明若 x2a(modm) 有解,设其中任意两解为 s,t,则有 s2t20(modp),而上面说明了这只能在 s=ts=t 时成立。

于是 12,22,,(p12)2 代表了模 p 的全部二次剩余,其余为模 p 的二次非剩余。

定理 3:模 p 的两个二次非剩余的乘积是模 p 的二次剩余。

证明:设 a,b 为模 p 的两个二次非剩余。根据 定理 1定理 2 可得,12a,22a,,(p12)2a 均为二次非剩余。且 ab 和它们模 p 两两不同余,于是 ab 只能为二次剩余。

勒让德符号

勒让德符号可以方便我们研究二次剩余:

定义(勒让德符号):

(ap)={0pa1paap1paap

根据 定理 3 可以得出,(abp)=(ap)(bp)

Euler 判别条件

判定二次剩余只需要求出对应的勒让德符号的值,接下来引入 Euler 判别条件,用于求解 p 为奇质数时的勒让德符号。

利用二次互反律的方法在 Competive Programming 中不优于 Euler 判别条件,故不作介绍。

定理 5(Euler 判别条件):设 aZp 为奇质数,则 ap12(ap)(modp)

证明:若 pa,则 (ap)ap120(modp)。于是不妨设 pa

(ap)=1,则 xZ:x2a(modp),于是 ap12xp11(modp)p 为奇质数,由费马小定理可得)。

(ap)=1。则 12,22,,(p12)2 代表了模 p 的所有二次剩余,且 12a,22a,,(p12)2a 代表了模 p 的所有二次非剩余(由 定理 2 可得),于是它们两两不同余,构成了 p 的一个简化剩余系。

于是 i=1p12i2ai2r=1n1r1(modp)。即 ap12(i=1p12i2)21(modp)。注意到左边后面一块带平方,于是里面的累乘可以不管符号,不妨把 i2 改写成 i2,于是有 ap12(i=1p12(pi)i)21(modp),即 ap12(p1)!21(modp),由于 (p1)!1(modp),于是 ap121(modp)

Cipolla 算法

Cipolla 算法可以求解 x2n(modp)p 为奇质数,x 是模 p 的二次剩余,且 pn),算法流程如下。

  1. 随机寻找一个 a,满足 a2n 是模 p 的二次非剩余。
  2. i2a2n(modp),这里由于 a2n 是模 p 的二次非剩余,需要扩域计算(类似复数)。
  3. 求出 (a+i)p+12 即为方程的一个解。

通过证明以下引理来证明算法的时间复杂度和正确性:

引理 1:第一步期望 2 次随机即可找到需要的二次非剩余。

证明:列出关于 x 的方程 x2a2n(modp),第一步即找出一个 a,使得这个方程无解。

移项可得 a2x2n(modp),分解平方差:(ax)(a+x)n(modp)

由于 pn,于是等号能成立当且仅当 ax0(modp)a+x0(modp)

在模 p 意义下,能满足 bcn(modp)(b,c) 恰有 p1 对,即 b=1,2,,p1,c=b1。将 ax 视为 ba+x 视为 c,则 (ax,a+x)p1 种。

不难发现,一个使方程有正整数解的 a 对应了两种不同的解 xx 是一个解,则 x 也是一个解),又因为 n 是二次剩余,于是使得方程有解 x=0a 恰有两个,而按照上面的讨论方式,ax=a+x 对应了两种情况。于是使得方程有解的 a 共有 p+12 个,剩下的 p12a 都会使方程无解。

p 足够大时,可以认为使得方程有解和无解的概率各占 12

引理 2(a+i)p+1n(modp)

证明:拆开:(a+i)p+1=(a+i)p(a+i)

对前面一项用二项式定理展开,注意到 1ip1:(pi)0(modp)(由 p 是奇质数容易得到)。于是 (a+i)pap+ip(modp)

由于 a2n 是模 p 的二次非剩余,于是根据 定理 5(Euler 判别条件)可得 (a2n)p121(modp),即 ip11(modp),由费马小定理得 apa(modp)

于是 (ap+ip)(a+i)(ai)(a+i)a2i2a2a2+nn(modp)

引理 3(a+i)p+12 在模 p 意义下的结果不含 i

证明:若存在一个 b+ci,满足 c0(modp),(b+ci)2n(modp),则 b2+2bci+c2(a2n)n(modp)

把“实部”和“虚部”分别放到两边:b2+c2(a2n)n2bci(modp),类似复数,等式成立当且仅当左右两边均为 0。考虑右边的“虚部”:由于 c0(modp),那么一定有 b0(modp),那么 (ci)2=c2(a2n)n(modp),于是 a2nn(c1)2(modp)。由于 n 是二次剩余,(c1)2 显然是二次剩余,于是 a2n 是二次剩余。

这和算法第一步要求的 a2n 是二次非剩余矛盾。

于是 Cipolla 算法可以在期望 O(logp) 的的时间内求解方程 x2n(modp)

实现

题目

#include <bits/stdc++.h>

using namespace std;

struct Complex {
    Complex() {r=i=0;};
    Complex(int v) {r=v;i=0;}
    Complex(int r,int i):r(r),i(i) {}
    int r,i;
};

int sq,mod;
mt19937 rng(random_device{}());

Complex operator+(const Complex &a,const Complex &b) {return Complex((a.r+b.r)%mod,(a.i+b.i)%mod);}
Complex operator*(const Complex &a,const Complex &b) {
    return Complex((1ll*a.r*b.r%mod+1ll*a.i*b.i%mod*sq%mod)%mod,(1ll*a.r*b.i%mod+1ll*a.i*b.r%mod)%mod);
}

Complex qpow(Complex a,int b) {
    Complex res=1;
    for(;b;b>>=1,a=a*a) {
        if(b&1) res=res*a;
    }
    return res;
}

int qpow(int a,int b,int mod) {
    int res=1;
    for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) res=1ll*res*a%mod;
    return res;
}

bool check(int n,int p) {return n?qpow(n,(p-1)/2,p)==1:true;}

int Cipolla(int n,int p) {
    mod=p; n%=mod;
    if(n==0) return 0;
    uniform_int_distribution<int> gen(0,p-1);
    int a=gen(rng);
    while(check((1ll*a*a%mod+mod-n)%mod,p)) a=gen(rng);
    sq=(1ll*a*a%mod+mod-n)%mod;
    return qpow(Complex(a,1),(p+1)/2).r;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr); cout.tie(nullptr);

    int T=0; cin>>T;
    while(T--) {
        int n,p; cin>>n>>p;
        if(!check(n,p)) cout<<"Hola!"<<"\n";
        else {
            int res=Cipolla(n,p);
            if(res>p-res) res=p-res;
            cout<<res;
            if(res) cout<<" "<<p-res;
            cout<<"\n";
        }
    }

    return 0;
}

本文作者:Transparent

本文链接:https://www.cnblogs.com/xzmxzm/p/cipolla.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   xzm111  阅读(107)  评论(0编辑  收藏  举报
评论
收藏
关注
推荐
深色
回顶
收起
点击右上角即可分享
微信分享提示