剩余问题
剩余问题
解决形如\(x^k \equiv a \pmod p\)的问题。暴力找似乎代价太高了。我们先从简单的入手。
二次剩余
若\(x^2 \equiv a \pmod p\)有解,则称\(a\)是\(p\)的二次剩余。否则称其为非二次剩余。
定义\(\text{Legendre}\)符号:
(长相奇怪,其实只是记号)
仅讨论\(p\)是奇素数的情况。此时有:
这个可以快速判断其是否为二次剩余。
\(Proof\):
我们证明了所有的二次剩余得到的结果都是\(1\),所有的非二次剩余得到的结果都是\(-1\),整除时为\(0\),而又不存在第四者,所以命题能成立。
然后有\(p\)是奇质数下,二次剩余和非二次剩余的数量一样多。这个很显然,让\(1\)到\(p-1\)的平方在模\(p\)意义下构成集合,而我们有:
所以我们只要考虑\(1\)到\(\frac{p-1}{2}\)之间的平方即可。
若有\(x^2 \equiv y^2 \pmod p\),\(x \not\equiv y\),则\(x^2-y^2 \equiv 0 \pmod p \Rightarrow p \mid (x-y)(x+y)\),由于\(p\)是质数,所以有\(p \mid x-y\)或\(p \mid x+y\)而只能\(x \equiv -y\),发现两数只能在\(\frac{p-1}{2}\)两边,所以得到\(1\)到\(\frac{p-1}{2}\)之间,两两的平方不同,而这样得到的二次剩余只有\(\frac{p-1}{2}\)个,非二次剩余也只有\(\frac{p-1}{2}\)个。
Cipolla
该算法是用来解决二次剩余问题的,思想基于构造。注意下文解决的是\(x^2 \equiv n \pmod p\),右边的是\(n\)而不是\(a\)了。一般解决\(p\)是质数的情况。
如果\(n\)是二次非剩余,显然无解;
如果\(n\)是二次剩余,我们随机一个\(a\),使得\(a^2-n\)为非二次剩余。由非二次剩余和二次剩余数量一样多,所以有\(\frac{1}{2}\)的概率取到,期望下\(2\)次即能得到。定义\(\omega = \sqrt{a^2-n}\),事实上\(\omega\)在整数域中没有数存在,所以类似于定义虚数单位\(i\)一样,存数也先存成\(a+b\omega\)这种形式,之后会消掉这个东西。
我们要\(x^2 \equiv a \pmod p\),先要知道这几个引理。
引理1:\((x+\omega)^p \equiv x^p + \omega^p \pmod p\)。
\(Proof\):
引理2:\(\omega^p \equiv -\omega\)。
\(Proof\):
结论:\(x=(a+\omega)^\frac{p+1}{2}\)
\(Proof\):
那这个式子咋求?直接上复数。根据拉格朗日定理,最后虚部一定为\(0\)。不会拉格朗日定理?其实我也不会那我们反证下吧。
\(Proof\):
还有一种情况就是\(p=2\),此时它是质数但不是奇质数,所以特判掉即可。
如果\(x\)有解,其实存在两组解,另一组解为\(p-x\)。
namespace Cipo {
#define rd() (1ll*RAND_MAX*rand() + rand()) // 自定义随机函数(范围够大)
int n, P, a, t;
struct comp { int x, y; };
comp operator * (comp a, comp b) {
return (comp){(1ll*a.x*b.x+1ll*a.y*b.y%P*t)%P, (1ll*a.x*b.y+1ll*a.y*b.x)%P}; // 类似于复数乘法
}
int qpow(int a, int b) {
int res = 1;
for (int i = a; b; i = 1ll*i*i%P, b >>= 1)
if (b & 1) res = 1ll*res*i%P;
return res;
}
comp qpow(comp a, int b) { // 定义复数快速幂
comp res = (comp){1, 0};
for (comp i = a; b; i = i*i, b >>= 1)
if (b & 1) res = res*i;
return res;
}
int sqrt(int n, int P) {
Cipo::n = n, Cipo::P = P;
if (P == 2) return n; // P=2特判
if (!n) return 0; // n=0特判
if (qpow(n, P-1>>1) != 1) return -1; // 非二次剩余直接排除
while (a = rd() % P, qpow(t = (1ll*a*a-n+P)%P, P-1>>1) != P-1); // 找出一个a
return qpow((comp){a, 1}, P+1>>1).x; // 答案
}
}
该算法的复杂度:\(\text{O}(\log p)\)。
附期望是\(2\)的证明:
K次剩余
定义同上,只不过把\(2\)换成了\(k\),但无法套用上面的解法求解。这里仍然假定\(p\)是奇质数。
发现构造走不通了!没关系,我们学习了原根!
众所周知原根遍历了所有与\(p\)互质的数,所以我们用原根替换它们。
比如说\(x^k \equiv a \pmod p\),设\(g\)为原根,\(a=g^s\),\(x=g^m\),则原式变成了\((g^m)^k \equiv g^s \pmod p\),也就是\(g^{km} \equiv g^s \pmod p\),\(s\)可以通过\(\text{BSGS}\)解得;\(m\)未知。然后由阶的性质得到\(km \equiv s \pmod{\varphi(p)}\),也就是\(km \equiv s \pmod{p-1}\),解\(m\)即可。
想要所有解?刚刚的同余方程的解在模\(p-1\)的意义下的所有取值即为所有解。事实上题目一般要求找出任意解,正常解同余方程即可。如果上面的任意一步都出了问题(比如说\(s\)无解,或者同余方程没有解),那就没有解了。
// 码量惊人,博主写了140+行。这里只放最核心部分
// 这里用K次剩余解决二次剩余问题
int solve(int k, int a, int P) { // x^k=a (%p)
int s = BSGS(g, a, P); // 找出s使得g^s=a
int d = gcd(k, P-1);
if (s == -1 || s % d) return -1; // BSGS无解或同余方程无解,说明该问题无解
return qpow(g, 1ll*(s/d)*inv(k/d, (P-1)/d)%P, P); // 解同余方程km=s(%phi(p))
}
int main() {
init(); // 初始化质数表
T = read();
while (T--) {
N = read(), P = read(); N %= P;
if (!N) { printf("0\n"); continue; }
if (P == 2) { printf("1\n"); continue; }
getG(P); // 得到模P的原根
int ans = solve(2, N, P); // 解决K次剩余就写成solve(K, N, P)
if (ans == -1) printf("Hola!\n");
else printf("%d %d\n", min(ans, P-ans), max(ans, P-ans));
}
return 0;
}

浙公网安备 33010602011771号