浅谈二次剩余
Ruiqun2009 · · 算法·理论
二次剩余,是方程 x^2\equiv a\pmod p 的解。
为了方便起见,这里的模数均为奇素数。(毕竟其他情况考的很少)
我们先引出一个符号:
勒让德符号。
然后我们再看这个东西:
当
当
发现这个东西和勒让德符号的关系了吗?
事实上,有这个东西:
欧拉判定准则。
它指出:
我们试着证明它。
引理:令 p 为素数和模 p 意义下原根 g 并令 a=g^k 。那么 x^2\equiv a\pmod p 有解当且仅当 k 为偶数。
引理的证明:
-
(充分性)假设
x^2\equiv a\pmod p 有解为g^l 对于某个l 成立。那么(g^l)^2\equiv a\pmod p\implies g^{2l}\equiv a\pmod p 。因此k\equiv2l\pmod{p-1} (费马小定理),而p-1 为偶数,所以k 为偶数。 -
(必要性)假设
k 为偶数,那么x^2\equiv g^k\pmod p\iff x^2\equiv (g^{k/2})^2\pmod p 。而因为k 为偶数,所以k/2 为整数,因此x^2\equiv g^k\pmod p 有解为g^{k/2} 。
因为
且根据阶的定义,对于所有
考虑同余方程
所以当
又因上述引理,
即得欧拉判别准则,也可以推断出勒让德符号为完全积性函数。
然后就是这篇文章最重要的环节:
\texttt{Cipolla} 算法!
这东西就是扩域的升级版。
现实中有个东西叫虚数,我们可以把虚数单位变成我们想要的。
现在我们要找虚数单位,方便我们计算。
这个算法的第一步是:
- 找到
m ,使得m^2-a 为\bmod p 意义下的非二次剩余。 - 令
i^2\equiv m^2-a\pmod p 。
这里你就有疑问了:既然
因为
然后我们计算
先证明一个引理:
我们将右边用二项式定理展开。得到:
然后我们发现这个式子在
所以,等式成立。
于是
由于费马小定理,
然后再根据定义,得到
于是我们证明了
例题实现:
#include <cstdio>
#define int long long
inline int qpow(int base, int power, int p) {
int ans = 1;
for (; power; base = base * base % p, power >>= 1)
if (power & 1) ans = ans * base % p;
return ans;
}
inline int mod(int a, int p) {
return (a % p + p) % p;
}
namespace Cipolla {
inline int legendre(int a, int p) {
return qpow(mod(a, p), (p - 1) / 2, p); // must mod first!
}
inline int find_not_sqrt(int n, int p) {
for (int a = 0; a < p; a++)
if (legendre(a * a - n, p) == p - 1) return a;
return -1;
}
int a, p, n;
struct cp {
int m_real, m_imag;
};
cp mul(cp _a, cp _b) {
int c = a * a - n;
return
cp{(_a.m_real * _b.m_real % p + _a.m_imag * _b.m_imag % p * c) % p,
(_b.m_imag * _a.m_real + _a.m_imag * _b.m_real) % p};
}
cp qpow(cp base, int power) {
cp ans{1, 0};
for (; power; base = mul(base, base), power >>= 1)
if (power & 1) ans = mul(ans, base);
return ans;
}
int query(int n, int p) {
if (n % p == 0) return 0;
if (legendre(n, p) != 1) return -1;
Cipolla::n = n;
Cipolla::p = p;
a = find_not_sqrt(n, p);
return mod(qpow(cp{a, 1}, (p + 1) >> 1).m_real, p);
}
}
void solve() {
int x, p;
scanf("%lld%lld", &x, &p);
int val = Cipolla::query(x, p);
if (val == -1) puts("Hola!");
else if (val == 0) puts("0");
else {
if (val > p - val) val = p - val;
printf("%lld %lld\n", val, p - val);
}
}
signed main() {
int T;
scanf("%lld", &T);
while (T--) solve();
}
练习:
- P1962 斐波那契数列
参考文献:
- 二次剩余 - OI-Wiki
- 算法学习笔记(41):二次剩余 - 知乎
后记:
- 任意模数二次剩余咋做?中国剩余定理!(后面会讲)
- 关于二次剩余的其他内容,参见本文.