二次剩余(Cipolla)
question
求解
使用(\(Cipolla\))算法,仅可求解 \(p\) 是奇素数的情况
解的个数
我们称一个数为二次剩余当且仅当存在 \(x^2 \equiv n\ (mod\ p)\) 否则为非二次剩余
考虑二次剩余 \(n\) 有多少个解
从 \(n\) 的解中任取出 2 个 \(x_0, x_1, x_0 \ne x_1\) 则
移项
已知 \(x_0 \ne x_1\) 则 \(x_0 + x_1 \equiv 0\)
由此可知 \(x_0, x_1\) 互为相反数,且每一对相反数都对应不同的二次剩余
而且由于 \(p\) 是奇素数 \(x_0 \ne x_1\)
那么我们知道了,奇素数 \(p\) 有 \(\frac {p - 1} {2}\) 个二次剩余
欧拉准则
我们知道了解的数量,如何判断一个数 \(n\) 是不是二次剩余呢?
设 \(n \ne 0\)
- \(n\) 是二次剩余 \(\iff n^{\frac{p-1}{2}} \equiv 1\)
引理1
- 证明
由欧拉定理得知
即 \(n ^ {\frac {p - 1} 2}\) 是 1 开方的结果,所以只能是 \(\pm 1\)
证明
设 \(g\) 为 \(p\) 的原根,\(n \equiv g^k\)
若 \(n ^ {\frac {p - 1} 2} \equiv 1\) 则 \(g ^ {k \frac {p - 1} 2} \equiv 1\)
因为 \(g\) 是原根 \((p - 1) | ( k \frac{p - 1} 2)\)
那么 \(k\) 一定是偶数
则 \(g^{\frac k 2}\) 就是 \(n\) 的一个解
由此我们得出如果 \(n ^ {\frac{p-1}{2}} \equiv 1\) 则 \(n\) 是二次剩余
如果 \(n\) 是二次剩余 \(n ^ {\frac{p - 1} 2} \equiv (x^2)^{\frac{p - 1} 2} \equiv x^{p - 1} \equiv 1\)
\(Cipolla\) 算法
对于 \(x^2 \equiv n\) 找到一个 \(a\) 使得 \(a^2 - n\) 是非二次剩余
这一步期望复杂度 \(O(2)\) 因为非二次剩余个数接近 \(\frac n 2\)
定义 \(i^2 \equiv a^2 - n, i\) 是类似复数一样的东西
所有数都可以表示成 \(A + Bi\)
\((a + i)^{\frac{p + 1} 2}\) 即为一个解
引理 1
- 证明
因为 \(a^2 - n\) 是非二次剩余,\((a^2 - n)^ {\frac{p - 1} 2} \equiv -1\)
引理 2
因为 \(p\) 是素数,所有包含 \(p\) 的阶乘都被模成了 \(0\) 只剩下了 \(\binom p 0 B^p + \binom p p A^p\)
证明
则 \((a + i)^{\frac{p + 1} 2}\) 就是答案
那么 \((a + i)^{\frac{p + 1} 2}\) 的虚部是否为 0 呢
假设存在 \((A + Bi) ^ 2 \equiv n, B \ne 0\)
左边虚部为 0 说明右边也为 0
则 \(AB \equiv 0\),因为 \(B \ne 0\) 则 \(A \equiv 0\),那么
因为 \(n\) 是二次剩余,设 \(x^2 \equiv n\)
\(i ^ 2\) 显然是二次剩余,与前提矛盾
证毕
板子
#include <bits/stdc++.h>
using namespace std;
#define I inline
#define int long long
int imuli, mod, n;
struct cpx{
int a, b;
cpx(int a, int b): a(a), b(b) {}
I cpx operator * (const cpx &rhs) const {
return cpx((a * rhs.a % mod + b * rhs.b % mod * imuli % mod) % mod, (a * rhs.b % mod + b * rhs.a % mod) % mod);
}
};
I cpx ksm(cpx a, int b){
cpx ans(cpx(1, 0));
while(b){ if(b & 1) ans = ans * a; b >>= 1; a = a * a; }
return ans;
}
I int ksm(int a, int b){
int ans = 1;
while(b){ if(b & 1) ans = ans * a % mod; b >>= 1; a = a * a % mod; }
return ans;
}
I bool check(int n){
return ksm(n, (mod - 1) / 2) == 1;
}
I void Main(){
cin >> n >> mod;
n %= mod;
if(!n) return cout << 0 << '\n', void();
if(n == 2) return cout << "1" << '\n', void();
if(!check(n)) return cout << "Hola!" <<'\n', void();
int a;
do{
a = (1ll * rand() + 1ll * rand() * rand()) % mod;
}while(check((a * a + mod - n) % mod));
imuli = (a * a % mod + mod - n) % mod;
int x0 = ksm(cpx(a, 1), (mod + 1) / 2).a, x1 = mod - x0;
if(x0 > x1) swap(x0, x1);
cout << x0 << " " << x1 << '\n';
}
signed main(){
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int T; cin >> T;
while(T--) Main();
return 0;
}
- \(tps\):
cpx
里面的b
可以% mod
是因为最终虚部为 0 剩下的b
在做乘法的时候需要% mod