N次剩余小记


前言

上周在 51nod 交了一些3、4级的题目,然后发现没有写过1级题,

就找到了一道 51nod 1014 \(X^2 \bmod P\) 的题目,当然这题虽然是暴力,但也可以用二次剩余做。

我就顺手看到了 51nod 1039 \(X^3 \bmod P\) (8级题)和 51nod 1038 \(X^A \bmod P\) (7级题)的题目。

本着模数都是质数那就尝试写一写,但在 \(X^3 \bmod P\) 用 BSGS 交一直 \(1.1s\) TLE 过不去,反而交到 \(X^A \bmod P\) 的题目那里就过了。

问了万能的 UOJ 群他们就推荐了三次剩余,就跑得飞快。

PS:这里的N次剩余模数是质数,不是一般情况,一般情况应该更加复杂。

如果只需要一个立方根,也可以在 LOJ 175 模立方根 中提交。


N次剩余(原根,BSGS,扩欧)

要解 \(X^A\equiv B\pmod{P}\),把它尝试转换成 \(A^X\equiv B\pmod{P}\) 的问题那就可以用 BSGS 解决。

考虑 \(P\) 的原根 \(G\),可以让 \([0,P-1]\)\(G^{0\sim P-1}\) 一一对应。

那么相当于 \(G^{kA}\equiv G^{B'}\pmod{P}\),那么先用 BSGS 将 \(B'\) 算出来。

然后相当于解决 \(kA\equiv B'\pmod{P-1}\) 的问题,用扩欧算出所有的 \(k\) 或者判无解。

然后最后的答案就是所有的 \(G^k\)

时间复杂度 \(O(\sqrt P)\)

代码:

#include 
#include 
#include 
#include 
#include 
using namespace std;
const int MOD=70001;
struct Linked_Hash{
	struct node{int y,w,next;}E[MOD]; int Et,hs[MOD];
	void Clear(){Et=0,memset(hs,-1,sizeof(hs));}
	void Insert(int w,int x){E[++Et]=(node){x,w,hs[w%MOD]},hs[w%MOD]=Et;}
	int locate(int W){
		for (int i=hs[W%MOD];~i;i=E[i].next)
		    if (E[i].w==W) return E[i].y;
		return -1;
	}
}ha;
int p,phi,P[MOD],ToT,g,a,m,ans[MOD],tot,prime[MOD],v[MOD],Cnt;
int iut(){
	int ans=0; char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
void print(int ans){
	if (ans>9) print(ans/10);
	putchar(ans%10+48);
}
void FJ(int p){
	for (int i=1;prime[i]*prime[i]<=p;++i)
	if (p%prime[i]==0){
		P[++ToT]=prime[i];
		while (p%prime[i]==0) p/=prime[i];
	}
	if (p>1) P[++ToT]=p;
}
int ksm(int x,int y,int p){
	int ans=1;
	for (;y;y>>=1,x=1ll*x*x%p)
	    if (y&1) ans=1ll*ans*x%p;
	return ans;
}
bool check(int x,int p){
	if (ksm(x,phi,p)!=1) return 0;
	for (int i=1;i<=ToT;++i)
	if (ksm(x,phi/P[i],p)==1) return 0;
	return 1;
}
int Mn_Rt(int p){
	for (int i=1;i=0&&i*ir>=j) return i*ir-j;
		now=1ll*now*a%mod;
	}
	return -1;
}
int exgcd(int a,int b,int &x,int &y){
	if (!b) {x=1,y=0; return a;}
	int Gcd=exgcd(b,a%b,y,x);
	y-=a/b*x;
	return Gcd;
}
int main(){
	for (int i=2;i

二次剩余(Cipolla's Algorithm)

\(a=0\) 或者 \(p=2\) 的情况,特判,只考虑奇素数的情况。

\(x^2\equiv a\pmod p\) 有解,那么 \(a\) 就是 \(p\) 的二次剩余,否则为非二次剩余。

定义勒让德符号 \(\left(\frac{a}{p}\right)=\begin{cases}1,a是p的二次剩余\\-1,a是p的非二次剩余\end{cases}\)

欧拉判别法:\(\left(\frac{a}{p}\right)=a^{\frac{p-1}{2}}(\gcd(a,p)=1)\)

如果 \(x^2\equiv a\pmod p\) 有解,那么 \(x^{p-1}\equiv 1\pmod p\),那么 \(a^{\frac{p-1}{2}}\equiv 1\pmod p\)

如果无解,考虑将 \(1\sim p-1\) 的数两两配对,使得\(\forall x,y\in[1,p),xy=a\),如果有解,一定会有 \(x\)\(p-x\) 无法配对。

既然配成了 \(\frac{p-1}{2}\) 对,它们的乘积就是 \((p-1)!\equiv -1\pmod p\) 所以 \(a^{\frac{p-1}{2}}\equiv -1\pmod p\)

然后有两个结论用来证明做法的正确性。

  1. \(x^2\equiv a\pmod p\) 有解,那么 \(a\) 的个数为 \(\frac{p-1}{2}\),相当于 \(a\)\(p-a\) 一个有解,一个无解。
  2. \((a+b)^p\equiv a^p+b^p\pmod p\),直接二项式定理展开 \(\binom{p}{i}\) 当且仅当 \(i=0\) 或者 \(i=p\)\(p\) 才能被消去,否则都会被 \(p\) 取模为0。

第一条结论说明随机出一个非二次剩余的期望次数为两次。

考虑随机出一个数 \(A\),使得 \(A^2-a\) 是一个非二次剩余,也就是 \((A^2-a)^{\frac{p-1}{2}}\equiv -1\pmod p\)

\(\omega=\sqrt{A^2-a}\),那么它应该是一个虚数,所有的复数都能用 \(x+y\omega\) 表示。

最后的答案 \(x\equiv (A+\omega)^{\frac{p+1}{2}}\pmod p\)

证明就是 \(x^2\equiv (A+\omega)^{p+1}\pmod p\)

后面这个东西因为第二条结论可以变成 \(x^2\equiv (A^p+\omega ^p)(A+\omega)\pmod p\)

因为 \((A^2-a)^{\frac{p-1}{2}}\equiv -1\pmod p\) 也就是 \({\omega}^{p-1}\equiv -1\pmod p\)

最后式子化简成 \(x^2\equiv (A-\omega)(A+\omega)\pmod p\)

因为 \(A^2-{\omega}^2=a\) 所以 \(x^2\equiv a\pmod p\)

那么总结一下做法:

特判 \(a=0\) 或者 \(p=2\) 的情况,然后如果 \(a^{\frac{p-1}{2}}\equiv -1\pmod p\) 那么无解。

然后随机出一个 \(A\),使得 \(A^2-a\)\(p\) 的非二次剩余,那么答案就是 \((A+\omega)^{\frac{p+1}{2}}\)

时间复杂度 \(O(2\log p)\)

代码:

#include 
#include 
#include 
using namespace std;
typedef long long lll; int n,mod,now;
int mo(int x,int y){return x+y>=mod?x+y-mod:x+y;}
struct CP{
	 lll x,y;
	 inline CP operator *(const CP &t)const{
	     return (CP){mo(x*t.x%mod,y*t.y%mod*now%mod),mo(x*t.y%mod,y*t.x%mod)};
	 }
};
int iut(){
	int ans=0; char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=ans*10+c-48,c=getchar();
	return ans;
}
void print(int ans){
    if (ans>9) print(ans/10);
    putchar(ans%10+48);
}
CP KSM(CP x,int y){
	CP ans=(CP){1,0};
	for (;y;y>>=1,x=x*x)
	    if (y&1) ans=ans*x;
	return ans;
}
int ksm(int x,int y){
	int ans=1;
	for (;y;y>>=1,x=1ll*x*x%mod)
	    if (y&1) ans=1ll*ans*x%mod;
	return ans;
}
int Cipolla(int n,int mod){
	if (!n) return 0;
	if (mod==2) return 1;
	if (ksm(n,(mod-1)>>1)==mod-1) return -1;
	for (int A=1;;){
		A=rand()%(mod-1)+1;
		now=mo(1ll*A*A%mod,mod-n);
		if (ksm(now,(mod-1)>>1)==mod-1){
			CP Ans=KSM((CP){A,1},(mod+1)>>1);
			return Ans.x;
		}
	}
}
int main(){
	for (int Test=iut();Test;--Test,putchar(10)){
		n=iut(),mod=iut();
		int Ans=Cipolla(n,mod);
		if (!n||mod==2) print(Ans);
		else if (Ans==-1) printf("Hola!");
		else{
			int _Ans=mod-Ans;
			if (Ans>_Ans) swap(Ans,_Ans);
			print(Ans),putchar(32),print(_Ans);
		} 
	}
	return 0;
}

三次剩余(Peralta Method Extension)

\(a=0\)\(p\leq 3\) 的情况特殊判断。

那么质数就可以在模三意义下分为一和二两种。

众所周知,一个三次单位根为 \(\omega=\frac{\sqrt{3}i-1}{2}\)

那么如果它是一个实数且方程有解,那么就有三个实数解,\(sqrt{p-3}\) 用二次剩余求出来。

也就是说如果找到一个实数解 \(x\),那么 \(x\omega\)\(x{\omega}^2\) 也是合法的实数解

如果 \(p\bmod 3=2\) 那么有且仅有一解 \(a^{\frac{2p-1}{3}}\),另外两个解都不是实数。

考虑 \(p\bmod 3=1\) 的情况,那么有解当且仅当 \(a^{\frac{p-1}{3}}\equiv 1\pmod p\)

引理:如果 \(k|p-1\),那么 \(a\)\(p\)\(k\) 次剩余当且仅当 \(a^{\frac{p-1}{k}}\equiv 1\pmod p\)

证明:充分性显然,考虑必要性的证明,设 \(p\) 的一个原根为 \(g\)\(g^t=a\)

那么在指数上,\(t\frac{p-1}{k}\equiv 0\pmod{p-1}\) 那也就是要保证 \(k|p-1\) 必要性也可以证明出来。

随机出三个数 \(A,B,C\) 构造多项式 \(f(x)=Ax^2+Bx+C,x^3=a\) 那么 \(f(x)^{p-1}\equiv 1\pmod p\)

那么如果 \(f(x)^{\frac{p-1}{3}}\) 只有一次项 \(B'\) 非0,那么 \((B'x)^3=1\)\(B'=x^{-1}\)

期望次数为9次,所以时间复杂度为 \(O(9\log p)\)

代码:

#include 
#include 
#include 
using namespace std;
typedef long long lll; int A,mod,now,NOW;
int mo(int x,int y){return x+y>=mod?x+y-mod:x+y;}
struct CP{
	 lll x,y;
	 inline CP operator *(const CP &t)const{
	     return (CP){mo(x*t.x%mod,y*t.y%mod*now%mod),mo(x*t.y%mod,y*t.x%mod)};
	 }
};
int iut(){
	int ans=0; char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=ans*10+c-48,c=getchar();
	return ans;
}
void print(int ans){
    if (ans>9) print(ans/10);
    putchar(ans%10+48);
}
CP KSM(CP x,int y){
	CP ans=(CP){1,0};
	for (;y;y>>=1,x=x*x)
	    if (y&1) ans=ans*x;
	return ans;
}
int ksm(int x,int y){
	int ans=1;
	for (;y;y>>=1,x=1ll*x*x%mod)
	    if (y&1) ans=1ll*ans*x%mod;
	return ans;
}
int Cipolla(int n,int mod){
	if (!n) return 0;
	if (mod==2) return 1;
	if (ksm(n,(mod-1)>>1)==mod-1) return -1;
	for (int A=1;;){
		A=rand()%(mod-1)+1;
		now=mo(1ll*A*A%mod,mod-n);
		if (ksm(now,(mod-1)>>1)==mod-1){
			CP Ans=KSM((CP){A,1},(mod+1)>>1);
			return Ans.x;
		}
	}
}
struct Three{
	lll f[3];
	inline Three operator *(const Three &t)const{
		Three F=(Three){0,0,0};
	    for (int i=0;i<3;++i)
	    for (int j=0;j<3;++j)
		if (i+j<3) F.f[i+j]+=f[i]*t.f[j]%mod;
		    else F.f[i+j-3]+=f[i]*t.f[j]%mod*NOW%mod;
		for (int i=0;i<3;++i) F.f[i]%=mod;
		return F;
	}
};
Three ksM(Three x,int y){
	Three ans=(Three){1,0,0};
	for (;y;y>>=1,x=x*x)
	    if (y&1) ans=ans*x;
	return ans;
}
Three Peralta(int A,int mod){
	if (!A) return (Three){0,-1,-1};
	if (mod<4) return (Three){A,-1,-1};
	if (mod%3==2) return (Three){ksm(A,(2*mod-1)/3),-1,-1};
	if (ksm(A,(mod-1)/3)!=1) return (Three){-1,-1,-1};
	int omega=1ll*(Cipolla(mod-3,mod)-1)*ksm(2,mod-2)%mod;
	Three X; NOW=A;
	while (1){
		X=(Three){rand()%mod,rand()%mod,rand()%mod};
		Three Ans=ksM(X,(mod-1)/3);
		if (!Ans.f[0]&&!Ans.f[2]&&Ans.f[1]){
			Ans.f[1]=ksm(Ans.f[1],mod-2);
			Ans.f[0]=Ans.f[1]*omega%mod;
			Ans.f[2]=Ans.f[0]*omega%mod;
			return Ans;
		}
	}
}
int main(){
	for (int Test=iut();Test;--Test,putchar(10)){
		mod=iut(),A=iut();
		Three Ans=Peralta(A,mod);
		if (Ans.f[0]==-1) printf("No Solution");
		else if (Ans.f[1]==-1) print(Ans.f[0]);
		else{
			sort(Ans.f,Ans.f+3);
			for (int i=0;i<3;++i)
			    print(Ans.f[i]),putchar(32);
		}
	}
	return 0;
}

后记

二次剩余来自 beginend的二次剩余Cipolla算法学习小记
三次剩余来自 skywalkert的寻找模质数意义下的二次剩余与三次剩余

相关