二次剩余
这篇题解还是经典的 Cipolla 算法,主要是本人结合了 3 篇题解后才弄懂该题,所以据我个人理解提供一个(也许)好懂的题解吧。
更好的阅读体验:blog
题目传送门
二次剩余,常被称为模意义开根,是求满足
的
当上面的方程有非
为方便书写,引入勒让德符号:
欧拉准则
用于判定
当模数
则有:
所以,当
欧拉准则给出公式:
也就是说,当
证明:我们来先来证“
-
充分性:
当
n\equiv x^2\pmod p 成立(即n 是模p 的二次剩余)时,有n^{\frac{p-1}{2}}\equiv x^{p-1}\equiv 1\pmod p ,证毕。 -
必要性:
设
g 是模p 的一个原根(因为p 是奇素数一定存在)(不知道原根的可以看一下 Pecco 大佬的文章:链接)
设n=g^k (gcd(n,p)=1 所以一定可以这样表示),那么有:n^\frac{p-1}{2}\equiv 1\equiv g^{\frac{k}{2}(p-1)}\pmod p 所以
\frac{k}{2}(p-1) 一定是\varphi(p)=p-1 的倍数,故k 一定是偶数。
此时令x=g^{\frac{k}{2}} 即可满足n\equiv x^2\pmod p 。所以此时n 是模p 意义下的二次剩余,证毕。
既然“
解的数量
对于方程
因为
所以
那么,一个二次剩余对应一对模意义下不同的相反数(
Cipolla
解模数为奇素数的二次剩余方程
算法流程:先找到一个
你可能觉得上述过程有问题:既然
其实这是类似于复数的概念,这里的
我们对于 Cipolla 算法的结论进行证明(每一步变形的说明将写在式子后面):
第一步变形:提出一个
第二步变形:
第三步变形:据费马小定理
后面的变形都是较为自然的了。
既然有
- tip:
(a+i)^{p+1} 的虚部一定为0 ,使用反证法可以证明。
实现过程中我们在
Code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read() {
ll x=0,f=1; char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') f=-1; ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0'; ch=getchar();}
return x*f;
}
struct num {
ll x;// 实部
ll y;// 虚部(即虚数单位√w的系数)
};
ll t,w,n,p;
num mul(num a,num b,ll p) {// 复数乘法
num res;
res.x=( (a.x*b.x%p+a.y*b.y%p*w%p) %p+p)%p;// x = a.x*b.x + a.y*b.y*w
res.y=( (a.x*b.y%p+a.y*b.x%p) %p+p)%p;// y = a.x*b.y + a.y*b.x
return res;
}
ll qpow_r(ll a,ll b,ll p) {// 实数快速幂
ll res=1;
while(b) {
if(b&1) res=res*a%p;
a=a*a%p;
b>>=1;
}
return res;
}
ll qpow_i(num a,ll b,ll p) {// 复数快速幂
num res={1,0};
while(b) {
if(b&1) res=mul(res,a,p);
a=mul(a,a,p);
b>>=1;
}
return res.x%p;// 只用返回实数部分,因为虚数部分没了
}
ll cipolla(ll n,ll p) {
n%=p;
if(qpow_r(n,(p-1)/2,p)==-1+p) return -1;// 据欧拉准则判定是否有解
ll a;
while(1) {// 找出一个符合条件的a
a=rand()%p;
w=( ((a*a)%p-n) %p+p)%p;// w = a^2 - n,虚数单位的平方
if(qpow_r(w,(p-1)/2,p)==-1+p) break;
}
num x={a,1};
return qpow_i(x,(p+1)/2,p);
}
int main() {
srand(time(0));
t=read();
while(t--) {
n=read(); p=read();
if(!n) {
printf("0\n");
continue;
}
ll ans1=cipolla(n,p),ans2=-ans1+p;// 另一个解就是其相反数
if(ans1==-1) printf("Hola!\n");
else {
if(ans1>ans2) swap(ans1,ans2);
if(ans1==ans2) printf("%lld\n",ans1);
else printf("%lld %lld\n",ans1,ans2);
}
}
return 0;
}
如有讲解错误或是疑惑之处欢迎在评论区提出。