二次剩余 & Cipolla算法

我不曾迷路过,不过是跌倒后再次爬起。

总感觉自己已经学过了甚至还有交题记录但是就是没有笔记。

二次剩余

如果存在 ,那么 的二次剩余。

并不是所有 都有二次剩余,这里我们着重了解有关 是奇质数的情况( 相信大家都会)。

勒让德符号

我们定义符号 表示勒让德符号,其具体定义如下:

  • 的二次剩余且, ,则
  • 不是 的倍数,且 的非二次剩余,那么
  • ,则

然后我们万能的欧拉得到了一个至关重要的准则,称为欧拉判别准则


Cipolla算法

算法仅能计算当 是奇质数的情况。那么根据欧拉判别准则,有 个数是模 意义下的非二次剩余。

有了上述暴论,我们随机出一个 使得 的非二次剩余,成功率大概是

然后我们定义 。但问题是 都不是二次剩余哪来的

然后我们打开脑洞,刚刚的所有讨论都是实数范围内,那如果我们把数域扩充到复数,当然会得到不同的结果。

所以我们定义复数域下的 为二次剩余,并用 表示,其中 为模 意义下的数,意义同于实部虚部。

首先有 ,这个证明较为容易:

然后考虑另外一个结论 ,这个证明也是比较容易的。考虑左式二项式展开,因为 是质数,所以对于 一定能被 整除。所以最后只剩下了 。约下来就是右式了。

于是我们得到了一个较为重要的结论:。有了上面的引理,这个证明也十分简单了:

所以我们解出了两个二次剩余解,为

但你发现 是个复数啊,这个解不应该也在复数域吗。显然是,但是通过证明, 的虚部为 。证明读者可自行阅读。

至于实现,像 那样写个复数即可,时间复杂度 ,这个是快速幂的 ,当然如果你霉到极致一直找不到 那没办法,但这种一般不可能。

参考实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
int Test;
std::mt19937 rnd((ll)new char);
ll N,Mod,w;
struct Complex
{
ll x,y;
Complex(ll x=0,ll y=0):x(x),y(y){}
friend Complex operator+(Complex &a,Complex &b)
{ return Complex((a.x+b.x)%Mod,(a.y+b.y)%Mod); }
friend Complex operator*(Complex &a,Complex &b)
{ return Complex(((a.x*b.x)%Mod+(a.y*b.y%Mod*w%Mod)%Mod+Mod)%Mod,((a.x*b.y)%Mod+(a.y*b.x)%Mod+Mod)%Mod); }
};
inline ll qPow(ll a,int b)
{
ll res=1;
for(;b;b>>=1,a=(ll)a*a%Mod) (b&1)&&(res=(ll)res*a%Mod);
return res;
}
inline ll qPow(Complex a,int b)
{
Complex res(1,0);
for(;b;b>>=1,a=a*a) (b&1)&&(res=res*a,1);
return res.x%Mod;
}
inline ll Cipolla(ll n,ll p)
{
n%=p;
if(qPow(n,(p-1)/2)==p-1) return -1;
ll a;
while(true)
{
a=rnd()%p;
w=(((a*a)%Mod-n)%Mod+Mod)%Mod;
if(qPow(w,(p-1)/2)==p-1) break;
}
Complex x(a,1);
return qPow(x,(p+1)/2);
}
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
read(Test);
while(Test--)
{
read(N,Mod);
if(!N){ puts("0");continue; }
ll ret=Cipolla(N,Mod);
if(ret==-1) puts("Hola!");
else
{
ll res=-ret+Mod;
if(ret>res) std::swap(ret,res);
if(ret==res) write(ret,'\n');
else write(ret,' ',res,'\n');
}
}
return 0;
}