二次剩余的判定及Cipolla算法

二次剩余

p p p是奇素数。所有的运算都是在群 Z p ∗ Z_{p}^{*} Zp中的运算。方程 x 2 = a ≠ 0 x^2=a \neq 0 x2=a̸=0问是否有解,以及解是什么?若有解, a a a就是模 p p p的二次剩余;若无解,则 a a a就是模 p p p的非二次剩余。

  1. a = 0 a=0 a=0,显然只有唯一解 x = 0 x=0 x=0.

  2. a ≠ 0 a\neq 0 a̸=0,有解等价于 a p − 1 2 = 1 a^{\frac{p-1}{2}}=1 a2p1=1;无解等价于 a p − 1 2 = − 1 a^{\frac{p-1}{2}}=-1 a2p1=1.

Z p ∗ Z_{p}^{*} Zp恰好有一半的元素是二次剩余,一半的元素不是二次剩余。当元素 a a a是二次剩余是,解有且只有两个 x 0 , x 1 x_0,x_1 x0,x1,且 x 0 = − x 1 x_0=-x_1 x0=x1,即解 x = ± c x=\pm c x=±c

因此,验证 a a a是否是二次剩余可以用快速模幂,复杂度 O ( log ⁡ 2 p ) O(\log_2{p}) O(log2p).

对于二次剩余 a a a,求解x使用[Cipolla]{.underline}(洋葱? 奇波拉?)算法。

Cipolla(洋葱?)算法

这是一个随机性算法,复杂度是 O ( log ⁡ 2 p ) O(\log_2{p}) O(log2p)

这个算法是在域 F p 2 F_{p^2} Fp2上进行运算的,在这个域上做乘法、幂运算,然后解的那个表达式算出来之后,一定是属于域 F p F_{p} Fp的, F p 2 F_{p^2} Fp2 F p F_{p} Fp的扩充。

  1. 使用随机的方法,找到一个满足 b b b满足 b 2 − a b^2-a b2a不是二次剩余。需要检验 b 2 − a b^2-a b2a是不是二次剩余的期望次数是 2 2 2.每次检验是 O ( log ⁡ 2 p ) O(\log_2{p}) O(log2p).

  2. 定义域 F p 2 F_{p^2} Fp2上的*“虚部单位”*为 ω \omega ω,其中 F p 2 = a + b ω ∣ a , b ∈ F p F_{p^2}={a+b\omega| a,b \in F_p} Fp2=a+bωa,bFp,且 ω 2 = b 2 − a \omega^2=b^2-a ω2=b2a.

  3. x 2 = a x^2=a x2=a的其中一个**解是 x = ( b + ω ) p + 1 2 . x=(b+\omega)^{\frac{p+1}{2}}. x=(b+ω)2p+1.*注意,虽然运算是 F p 2 F_{p^2} Fp2上的,但是右边的那个式子算出来的结果刚好是"虚部"*为0的元素。原因是 x 2 = a ( x ∈ F p ) x^2=a \quad (x \in F_p) x2=a(xFp) x 2 = ( x a + x b ω ) 2 = a + 0 ω ( x ∈ F p 2 , x a , x b ∈ F p ) x^2=(x_a+x_b\omega)^2=a+0\omega \quad (x \in F_{p^2},x_a,x_b \in F_p) x2=(xa+xbω)2=a+0ω(xFp2,xa,xbFp)这两个方程都有且仅有两个解。而前面方程的解显然是后面两个方程的解,所以前面方程的解。所以前面这个方程和后面这个方程的解是完全一样的。而对于后面这个方程 x = ( b + ω ) p + 1 2 x=(b+\omega)^{\frac{p+1}{2}} x=(b+ω)2p+1代入后面的方程去验证(为了计算简洁,先证明了一些性质),会发现它的确是解,于是这个式子就是一开始方程的解。

证明

判别准则的证明

a ≠ 0 a\neq 0 a̸=0,有解等价于 a p − 1 2 = 1 a^{\frac{p-1}{2}}=1 a2p1=1;无解等价于 a p − 1 2 = − 1 a^{\frac{p-1}{2}}=-1 a2p1=1.
以上这个命题的证明,主要分为——
这个的证明主要是需要用到 Z p ∗ Z_p^* Zp是个循环群,每个元素都可以表示成生成元的幂次的形式。
首先,明确一点,因为 ∀ a ∈ Z p ∗ , a p − 1 = 1 \forall a \in Z_p^*,a^{p-1}=1 aZp,ap1=1,再运用根据二次探测定理,容易得到 a p − 1 2 a^{\frac{p-1}{2}} a2p1取值只能是 ± 1 \pm1 ±1,即 1 1 1 p − 1 p-1 p1.

  • 有解,即 a a a是二次剩余,则 a p − 1 2 a^{\frac{p-1}{2}} a2p1算出来是,这个将 a a a换成 x 2 x^2 x2,再根据费马小定理易得。
  • 无解,即 a a a不是二次剩余,容易推出 a a a表示成生成元 b b b的幂次的形式( b k b^k bk)中的 k k k只能是奇数(否则,取 x = b k 2 x=b^{\frac{k}{2}} x=b2k就可以导出 x 2 = a x^2=a x2=a)。将 k k k表示成 2 u + 1 2u+1 2u+1的形式,然后将 a = b 2 u + 1 a=b^{2u+1} a=b2u+1代入 a p − 1 2 a^{\frac{p-1}{2}} a2p1容易得到 a p − 1 2 = b p − 1 2 a^{\frac{p-1}{2}}=b^{\frac{p-1}{2}} a2p1=b2p1.因为 b b b是生成元,所以阶是 p − 1 p-1 p1,指数小于 p − 1 p-1 p1时,算出来都不是单位元 1 1 1.所以 b p − 1 2 = − 1 b^{\frac{p-1}{2}}=-1 b2p1=1(别忘了只能取 ± 1 \pm1 ±1).
    虽然以上每一点都只进行了单向推导,但是以上两点综合起来,就说明了“ a ≠ 0 a\neq 0 a̸=0,有解等价于 a p − 1 2 = 1 a^{\frac{p-1}{2}}=1 a2p1=1;无解等价于 a p − 1 2 = − 1 a^{\frac{p-1}{2}}=-1 a2p1=1.”

解的证明

算法构造的解,运算是在 F p 2 = x ∣ x = a + b ω ∧ a , b ∈ F p F_{p^2}={x|x=a+b\omega \wedge a,b \in F_p} Fp2=xx=a+bωa,bFp,其中 ω 2 = b 2 − a \omega^2=b^2-a ω2=b2a.
对于 < F p 2 , + , ∗ > <F_{p^2},+,*> <Fp2,+,>
并且定义了 ( a 1 + b 1 ω ) + ( a 2 + b 2 ω ) = ( a 1 + b 1 ) + ( b 1 + b 2 ) ω (a_1+b_1\omega)+(a_2+b_2\omega)=(a_1+b_1)+(b_1+b_2)\omega (a1+b1ω)+(a2+b2ω)=(a1+b1)+(b1+b2)ω,
( a 1 + b 1 ω ) × ( a 2 + b 2 ω ) = ( a 1 b 1 + b 1 b 2 ω 2 ) + ( b 1 a 2 + a 1 b 2 ) ω (a_1+b_1\omega) \times (a_2+b_2\omega)=(a_1b_1+b_1b_2\omega^2)+(b_1a_2+a_1b_2)\omega (a1+b1ω)×(a2+b2ω)=(a1b1+b1b2ω2)+(b1a2+a1b2)ω
定义完了之后,可以通过基本的代数知识,证明 < F p 2 , + , ∗ > <F_{p^2},+,*> <Fp2,+,>是一个域。
原方程是在 x 2 = a x^2=a x2=a F p F_p Fp域内,而将方程的研究域改为 F p 2 F_{p^2} Fp2之后。
无论是原域还是新域,解的个数至多只有两个。(这个是拉格朗日定理得出来的,暂时不懂朗格朗日定理,搁置,记结论)。因此,a是二次剩余的情况下,原方程有解 x = ± c x=\pm c x=±c,这两个解显然是新域下的解。又因为新域下没有更多解,所以新域下的解就是原域下的解。
因此,证明就只剩下验证 x = ( b + ω ) p + 1 2 x=(b+\omega)^{\frac{p+1}{2}} x=(b+ω)2p+1符合方程 x 2 = a x^2=a x2=a即可。
直接代入计算就好了。只是由于为了计算比较简洁,有条理,不会算不下去,先给出了不少性质(定理)。这个别的博客基本都有,就不复言了。

下面为模板:

F p 2 F_{p^2} Fp2乘法与幂运算模板

应当放在**mod_sys(见快速乘幂)**的模板前面。

	// 二次剩余模板,注意,各个函数都假设p^2即mod^2不会爆ll
	// 域F_p的拓展域F_{p^2}
	struct F_p2_node{ll a,b;}; // F_p2_node所有的运算都应该通过F_p2_sys来管理调用。
	// 预设所有传入参数都是合法的。
	struct F_p2_sys{
		ll p,w2;
		typedef F_p2_node nd;
		nd mlt(const nd&x, const nd&y) const {
			nd ans;
			//(x.a+x.bw)(y.a+y.bw)
			ans.a = (x.a*y.a%p+x.b*y.b%p*w2%p)%p;
			ans.b = (x.b*y.a%p+x.a*y.b%p)%p;
			return ans;
		}
		// pre n>=0 非递归版 不可改成引用!
		nd pow(nd a, ll n) const {
			if (n == 0) {return nd{1,0};}
			// 始终维持要求的数可以表示为(a)^n*t
			nd t{1,0};
			while (n > 1)
			{
				if (n&1) t = mlt(t,a);
				n >>= 1; a = mlt(a,a);
			}
			return mlt(t,a);
		}
	};

Cipolla洋葱算法实现模板

需要使用先用快速乘幂的模板中的mod_sys,实际上,这个模板只是给那个mod_sys增加了成员函数。

struct mod_sys{
	// template code from 快速乘幂 见前面章节
	some code....

	// 预设a \not\equiv 0 (mod mod) 返回a是否是二次剩余
	// 预设mod是奇素数
	// 预设p^2不会爆long long,使用pow 若爆则改用pow_v2或者__int128
	bool is_quadratic_residue(ll a) {
		return 1 == pow(a,mod-1>>1);
	}

	// 解方程x^2 = a (mod mod)  p = mod 是奇质数
	// 返回是否有解。如果有x0,x1(x0<=x1)存储解 仅解是0的时候取等号
	// 洋葱算法。
	// 预设p^2不会爆long long,使用pow 若爆则改用pow_v2或者__int128
	bool sqrt(ll a,ll& x0, ll& x1) { // 需要一个随机数生成器
		a = to_std(a);
		if (a == 0) {x0 = x1 = 0; return true;}
		if (!is_quadratic_residue(a)) return false;
		uniform_int_distribution<> dis(1, mod-1);
		ll b,w2;
		while(true) {
			b = dis(gen);
			w2 = to_std((b*b)%mod-a);
			if (!is_quadratic_residue(w2)) break;
		}
		// x = (b+w)^{(p+1)/2}
		F_p2_sys fp2{mod,w2}; // p,w2
		F_p2_node t{b,1}; // a,b(means a+bw)
		auto x = fp2.pow(t,mod+1>>1);
		x0 = to_std(x.a); // assert(x.b==0);
		x1 = mod-x.a;
		if (x0 > x1) swap(x0,x1);
		return true;
	}
};

你可能感兴趣的:(acm模板,学习笔记,算法)