二次剩余的判定及Cipolla算法

二次剩余

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

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

  2. a0a\neq 0,有解等价于ap12=1a^{\frac{p-1}{2}}=1;无解等价于ap12=1a^{\frac{p-1}{2}}=-1.

ZpZ_{p}^{*}恰好有一半的元素是二次剩余,一半的元素不是二次剩余。当元素aa是二次剩余是,解有且只有两个x0,x1x_0,x_1,且x0=x1x_0=-x_1,即解x=±cx=\pm c

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

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

Cipolla(洋葱?)算法

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

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

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

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

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

证明

判别准则的证明

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

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

解的证明

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

下面为模板:

Fp2F_{p^2}乘法与幂运算模板

应当放在**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;
	}
};
posted @ 2019-09-09 03:07  我云知世就是力量  阅读(690)  评论(0)    收藏  举报