浅谈二次剩余

浅谈二次剩余

前置的好吃芝士

若存在整数 \(x^2\equiv n(\bmod p)\)\(p\) 为奇素数,则称 \(n\) 为二次剩余。否则称 \(n\) 为非二次剩余。

一般不考虑 \(n=0\) 的情况。

下文同余皆在 \(\bmod p\) 的意义下,且 \(p\) 为奇素数。

一些性质:(?

\(x_0\)\(x_1\) 都满足 \(x_{0/1}^2\equiv n\) 且不相等,则有 \(x_0^2\equiv x_1^2\),移过去就是 \((x_0+x_1)(x_0-x_1)\equiv 0\)。而 \(x_0-x_1\) 是不可能为 \(0\) 的,所以 \(x_0\)\(x_1\) 为一对相反数。

  • \(x_0+x_1\equiv 0\)

所以任意一对相反数都对应一个二次剩余,且它们两两不同。

所以二次剩余与非二次剩余的数量都是 \(\frac{p-1}{2}\)

判定

因为 \(n^{p-1}\equiv 1\),所以 \(n^{2(\frac{p-1}{2})}\equiv 1\),所以 \(n^{\frac{p-1}{2}}\)\(1\) 的二次剩余。

所以 \(n^{\frac{p-1}{2}}\equiv \pm 1\)

  • \(n^{\frac{p-1}{2}}\equiv \pm 1\)

\(n\) 是二次剩余,则存在 \(x^2\equiv n(\bmod p)\),所以 \(n^{\frac{p-1}{2}}\equiv (x^2)^{\frac{p-1}{2}}\equiv x^{p-1}\equiv 1\)

  • \(n^{\frac{p-1}{2}}\equiv 1\),其中 \(n\) 为二次剩余。

如果满足 \(n^{\frac{p-1}{2}}\equiv 1\),则 \(n\) 是二次剩余。

这个逆命题要用到原根,我不会证/wul

所以!如果 \(n^{\frac{p-1}{2}}\equiv -1\)\(n\) 就为非二次剩余。因为 \(n^{\frac{p-1}{2}}\equiv \pm 1\)

  • \(n^{\frac{p-1}{2}}\equiv 1\) 等价于 \(n\) 为二次剩余。
  • \(n^{\frac{p-1}{2}}\equiv -1\) 等价于 \(n\) 为非二次剩余。

Cipolla 算法

Cipolla 算法就是找到 \(x^2\equiv n(\bmod p)\)\(x\) 的算法。

对于二次同余方程 \(x^2\equiv n\)

找到 \(a\) 满足 \(a^2-n\) 是非二次剩余。

由于二次剩余与非二次剩余的数量是对半开的,所以你随机 rand,期望两次就能抽到。

(你听说过非酋吗?)

接下来定义 \(i^2\equiv a^2-n\)

上文提到,\(a^2-n\) 是非二次剩余,所以理论上是不存在这个 \(i\) 的。

所以我们参考从实数域扩展到复数域的先例。不需要知道 \(i\) 是多少,就把它当作一个字母。

同复数一样地,我们定义 \((a+bi)\times (c+di)=(ac+bdi^2)+(ad+bc)i\)

其中 \(i^2\) 就用 \(a^2-n\) 代替。

一些引理:

  • \(i^p\equiv -i\)

证明:

\[i^p\\ \equiv i(i^2)^{\frac{p-1}{2}}\\ \equiv i(a^2-n)^{\frac{p-1}{2}}\\ \equiv -i \]

因为上文提到,\(a^2-n\) 是非二次剩余,所以 \((a^2-n)^{\frac{p-1}{2}}\equiv -1\)

  • \((a+b)^p\equiv a^p+b^p\)

证明:(要用到二项式定理)

\[(a+b)^p\\ \equiv \sum_{i=0}^p \tbinom{p}{i}a^ib^{p-i}\\ \]

然后我们注意到组合数 \(\tbinom{p}{i}\)

除了 \(\tbinom{p}{0}=\tbinom{p}{p}=1\) 外,所有的 \(\tbinom{p}{i}\) 都等于 \(p(p-1)(p-2)\cdots (p-i+1)\)

注意到我们的运算是在模 \(p\) 的意义下的,所以 \(\tbinom{p}{i}\) 一定是 \(0\)

所以 \((a+b)^p\) 就只剩下了 \(a^0 b^p+a^p b^0=a^p+b^p\)

然后证明最终公式:\((a+i)^{p+1}\equiv n\)

证明:

\[(a+i)^{p+1}\\ \equiv (a+i)^p(a+i)\\ \equiv (a^p+i^p)(a+i)\\ \because a^p=a^{p-1}a\wedge a^{p-1}\equiv 1(\mod p)\\ \therefore\\ (a^p+i^p)(a+i)\equiv (a-i)(a+i)\\ \equiv a^2-i^2\\ \because i^2\equiv a^2-n\\ \therefore a^2-i^2\equiv a^2-a^2+n\equiv n \]

所以!

\((a+i)^{\frac{p+1}{2}}\) 是一个解,它的相反数是另一个解。

综上,我们只要随机找到一个 \(a\),满足 \(a^2-n\) 是非二次剩余,再计算 \((a+i)^{\frac{p+1}{2}}\) 与它的相反数就好了。

那么这个 \(i\) 该怎么表示呢?

显然,因为 \(i\) 的意义十分抽象复杂,别说整数域,就连实数域都没有它。所以在答案中 \(i\) 的系数一定是 \(0\)

证明:

假设存在 \((A+Bi)^2\equiv n\),即对于方程 \(x^2\equiv n\),令 \(A+Bi=x\)。其中 \(B\neq 0\)

那么:

\[(A+Bi)^2\equiv n\\ A^2+B^2i^2+2ABi\equiv n\\ \because i^2=a^2-n\\ \therefore A^2+B^2(a^2-n)+2ABi\equiv n \]

因为 \(n\) 是正整数,而 \(i\) 成分复杂,所以 \(2AB\) 一定等于 \(0\)

而我们约定了 \(B\neq 0\),所以 \(A\equiv 0\)

回到原式,得 \(B^2i^2\equiv n\)

进一步推式子,有 \((Bi)^2\equiv n\)

也就是 \(i^2\equiv \frac{n}{B^2}\),即 \(i^2\equiv B^{-2}\)

\(B^{-2}\equiv (B^{-1})^2\),但是 \(i^2\) 是一个非二次剩余,所以与 \((B^{-1})^2\) 是二次剩余矛盾。

证毕。

综上,总结一下 Cipolla 算法的过程:

  • rand 出一个 \(a\),满足 \((a^2-n)^{\frac{p-1}{2}}\equiv -1\)
  • \(i^2=a^2-n\)
  • 用快速幂求出 \((a+i)^{\frac{p+1}{2}}\)。这是原式的一个解。
  • 找到它的相反数,这是另一个解(如果有的话)。

相反数直接用模数减去答案就行了。

模板题&代码

link。

注意判断一下 \(n\)。如果不是二次剩余直接输出。

还有找 \(a\) 的时候一定快速幂后是转化成 \(p-1\),而非真的 \(-1\)

别问。问就是自己栽过(((

Code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ljl;
int T;
ljl n,p,I2;
ljl rnd(){return rand()%p*rand()%p;}
struct C{//a+bi 类似于复数的类
	ljl a,b;
	C(){a=0;b=0;}
};
C operator * (C a,C b)//类似复数乘法
{
	C ans;
	ans.a=(a.a%p*b.a%p+a.b%p*b.b%p*I2%p)%p;
	ans.b=(a.a%p*b.b%p+a.b%p*b.a%p)%p;
	return ans;
}
C qpow(C a,ljl k)
{
	C ans;ans.a=1;ans.b=1;
	while(k)
	{
		if(k&1)
			ans=ans*a;
		a=a*a;
		k>>=1;
	}
	return ans;
}
bool check()
{
	C t;t.a=I2;t.b=0;
	return qpow(t,(p-1)/2).a==p-1;
}
bool checkn()//如果 n 是非二次剩余直接结束了
{
	C t;t.a=n;
	if(qpow(t,(p-1)/2).a==p-1)return 0;
	return 1;
}
void Main()
{
	cin>>n>>p;
	if(!n){cout<<"0\n";return;}
	if(!checkn()){cout<<"Hola!\n";return;}
	ljl a=rnd();I2=(a*a+p-n)%p;
	while(!a||!check())
	{
		a=rnd();
		I2=(a*a+p-n)%p;
	}
	C t;t.a=a;t.b=1;
	ljl ans0=qpow(t,(p+1)/2).a;
	ljl ans1=p-ans0;
	cout<<min(ans0,ans1)<<' '<<max(ans0,ans1)<<'\n';
	return;
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	srand(rand());
	cin>>T;
	while(T--)
		Main();
	return 0;
}

完结撒花!

posted @ 2026-03-16 00:13  Atserckcn  阅读(38)  评论(0)    收藏  举报