二次剩余
xiezheyuan · · 算法·理论
定义
我们定义一个数
例如
有一个基本的观察,这个同余方程只会有两个解
Legendre 符号
定义 Legendre 符号表示:
下面是 Wikipedia 上给出的 Legendre 符号表:
Euler 准则
定理(Euler's Criterion):对于奇素数
p 和一个整数n ,有:\left(\frac{n}{p}\right)\equiv n^{(p-1)/2}\pmod{p} 也就是说,若
n^{(p-1)/2}\equiv 1\pmod{p} ,那么n 是一个模p 意义下的二次剩余。
证明
若
首先根据费马小定理,有
因此只需要证明
必要性:
若
充分性:
根据原根存在定理,可取模
代入,得到
根据费马小定理,得到
那么取
Cipolla 算法
Cipolla 算法用于找到同余方程
先利用 Euler 准则判定是否存在解。故下面只讨论有解的情况。
我们任意选择一个
那么令
注意到一定是不存在整数
于是你发现这个解虚部总是
正确性证明
容易发现只需要证明
注意到
然后我们需要知道一个引理:
(a+b)^p\equiv a^p+b^p\pmod{p}
证明:考虑应用二项式定理:
注意到若
于是
对于
根据 Euler 准则,有
因此我们得到
所以
时间复杂度证明
在算法流程中有一行:
我们任意选择一个
a ,使得a^2-n 非二次剩余(可以用 Euler 准则判定)。
那么需要证明
首先给出一个引理:
对于奇素数
p ,模p 意义下的二次剩余共有\frac{p-1}{2} 个。
证明非常简单。由于形如
那么任意取一个数
因此,可以近似看成每一次选取
所以时间复杂度为
代码实现
下面给出模板题的实现:
#include <bits/stdc++.h>
using namespace std;
struct cipolla_complex {
int x, y, us, mod;
cipolla_complex(int _x, int _y, int _us, int _mod): x(_x), y(_y), us(_us), mod(_mod) {}
cipolla_complex operator*(const cipolla_complex &rhs){
return cipolla_complex(
(1ll * x * rhs.x + (1ll * y * rhs.y % mod) * us) % mod,
(1ll * x * rhs.y + 1ll * y * rhs.x) % mod,
us, mod
);
}
};
cipolla_complex fastpow(cipolla_complex a, int n){
cipolla_complex res = {1, 0, a.us, a.mod};
while(n){
if(n & 1) res = res * a;
a = a * a, n >>= 1;
}
return res;
}
int fastpow(int x, int y, int mod){
int ret = 1;
while(y){
if(y & 1) ret = 1ll * ret * x % mod;
x = 1ll * x * x % mod, y >>= 1;
}
return ret;
}
mt19937 rnd(chrono::steady_clock::now().time_since_epoch().count());
int cipolla(int n, int mod){
if(fastpow(n, (mod - 1) >> 1, mod) != 1) return -1;
int a, us;
do {
a = rnd() % mod, us = ((1ll * a * a + mod) - n) % mod;
} while(fastpow(us, (mod - 1) >> 1, mod) != mod - 1);
return fastpow(cipolla_complex(a, 1, us, mod), (mod + 1) >> 1).x;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int t; cin >> t;
while(t--){
int n, mod; cin >> n >> mod;
if(n == 0) {cout << 0 << '\n'; continue; }
int ans = cipolla(n, mod);
if(!(~ans)) cout << "Hola!" << '\n';
else cout << min(ans, mod - ans) << ' ' << max(ans, mod - ans) << '\n';
}
return 0;
}
// Written by xiezheyuan