高次同余方程,二次同余方程学习笔记

写在前面

文章作者实力有限,本文可能有个别错误,如有错误请友好地指出。
高次同余方程就是\(x^a\equiv b(mod\ p)\)
二次同余方程就是\(x^2 \equiv b(mod \ p)\)
我们接下来讨论解这两种方程的方法。
那么有一个问题。既然知道了高次同余方程的解法,就可以直接用解高次同余的方法解二次剩余方程。为什么要单独学二次同余方程呢。
因为我区间加区间修改用的是线段树不是树套树。即问题特殊化之后可以使用一些特殊的方法,这种方法可能会比一般方法高效,简便。

正文

高次同余方程

首先需要知道原根

定义

满足\(a^x\equiv 1(mod \ p)\)的最小的正整数x是a关于模p的,接下来\(a\)的阶表示为\(<a>\)

条件

\(gcd(a,p)=1\),这是显然的。

性质

1\(<a>\mid \varphi(p)\)
证明:
因为\(a^{\varphi(p)}\equiv 1(mod\ p)\)\(a^{<a>}\equiv 1(mod\ p)\)
所以\(a^{\varphi(p)-<a>}\equiv 1(mod\ p)\)
进而得出\(a^{\varphi(p)\ mod \ <a>}\equiv 1(mod\ p)\)
假设\(<a>\)不是\(\varphi(p)\)的约数。
\(\varphi(p)\ mod<a>\neq 0\)
\((\varphi(p)\ mod<a>)\ < \ (<a>)\)\(<a>\)的最小性矛盾,与假设矛盾原命题成立。
证毕。

2:设a关于模p的阶为\(<a>\)\(a^0\)\(a^1\)\(a^2\),...,\(a^{<a>-1}\)两两不同
证明:
假设\(a^x\equiv1(mod\ p)\)\(a^y\equiv 1(mod \ p)\),且\(a^x\equiv a^y(mod\ p)\)\(x<y<<a>\)
\(a^{y-x}\equiv 1(mod \ p)\)\(y-x<<a>\)与阶的最小性矛盾,假设不成立,原命题成立。
证毕。

3:设a关于模p的阶为\(<a>\),则\(a^x\equiv a^y(mod\ p)\)的充要条件是\(x\equiv y(mod\ <a>)\)
两边一直除\(a^{<a>}\)因为\(a^{<a>}\equiv 1(mod\ p)\)所以结论显然。

原根

(因为原根的定义涉及到阶,所以默认互质,又因为原根的性质3,p为质数)

定义

满足g关于模p的阶等于\(\varphi(p)\)的g是p的一个原根。

性质

1:设g是模p的一个原根,那么对于任意一个小于p的正整数x,都存在唯一一个值r使得r是以g为底的x对模p的指数。
其实就是说\(g^r\equiv x(mod\ p)\)的r是唯一的,反过来也一样。其中g为p的一个原根,\(x<p\)\(r<\varphi (p)\)其实就是阶的第二个性质。不再赘述。
2:对于一个质数,所拥有的原根个数是\(\varphi(\varphi(p))\)
3:一个模数存在原根的从要条件是,这个模数可以表示为\(1,2,4,p,2p,p^n\)其中\(p\)是奇质数,n是任意正整数
(这两个性质的证明可能会用到抽象代数的知识,我不会555...)


那么如何找原根?

考虑到最小的原根一般比较小,所以我们枚举,原根然后判断。
假设我们当前枚举到\(i\),判断的时候只需要枚举\(\varphi(p)\)的质因子\(p1,p2,p3...pk\)。然后判断\(i^{\frac{\varphi(p)}{pj}}\)是不是全部都不是1,如果全部都不是1,\(i\)就是\(p\)的一个原根。
为什么?
我们这个过程其实是枚举\(\varphi(p)\)的约数,因为阶的性质1,所以i的阶是\(\varphi(p)\)的一个约数,我们只需要看有没有一个\(\varphi(p)\)的约数\(x\)使得\(i^x \equiv 1(mod \ p)\)就能判断i是不是原根了。
那为什么不直接枚举约数,用这种枚举方法对吗?
\(\varphi(p)\)的约数至少比\(\varphi(p)\)缺少一个质因子,因为如果\(x^a\equiv 1\)那么\(x^{ab}\equiv 1\),所以少多个质因子的情况被少一个质因子的情况包含,所以枚举所有少掉一个质因子的情况即\(\frac{\varphi(p)}{pj}\)就行。


解高次同余方程的步骤

首先要会BSGS和exgcd
\(x^a\equiv b(mod\ p)\)
1:先求出\(p\)的一个原根\(g\)
2:求出以\(g\)为底的b关于模p的指数r,即\(g^r\equiv b(mod\ p)\)(离散对数,用BSGS求解)
3:令\(x\equiv g^y(mod\ p)\),带回原式就是求\(g^{ay} \equiv g^r(mod\ p)\)
4:根据阶的性质3就是求\(ay\equiv r(mod\ p)\)(一次同余方程用exgcd求解)
5:在0~\(\varphi(p)-1\)中求得y的解,带回\(x\equiv g^y(mod\ p)\)解完方程。

有些值得注意的点:

因为原根的性质1求出的答案没有重复。
为什么要用原根代换b,别的数不行吗?我们需要用原根表示b。见原根的性质1
因为要求原根且p为质数所以试用的p的范围很小,见原根的性质3
只要求求出通解的复杂度为\(O(\sqrt p)\)
下面是输出所有解的代码。。。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=201010;
unordered_map<int,int> hsh;
int prime[N],tot,T,phi,ans[N],num,a,b,p,g,x,y;
int read(){
	int sum=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
	return sum*f;
}
void work(int x){
	int tmp=x;
	tot=0;
	for(int i=2;i*i<=x;i++){
		if(tmp%i==0){
			prime[++tot]=i;
			while(tmp%i==0)tmp/=i;
		}
	}
	if(tmp>1)prime[++tot]=tmp;
}
int ksm(int x,int b){
	int tmp=1;
	while(b){
		if(b&1)tmp=tmp*x%p;
		b>>=1;
		x=x*x%p;
	}
	return tmp;
}
int BSGS(int a,int b){
	hsh.clear();
	int block=sqrt(p)+1;
	int tmp=b;
	for(int i=0;i<block;i++,tmp=tmp*a%p)hsh[tmp]=i;
	a=ksm(a,block);
	tmp=1;
	for(int i=0;i<=block;i++,tmp=tmp*a%p){
		if(hsh.count(tmp)&&i*block-hsh[tmp]>=0)return i*block-hsh[tmp];
	}
}
int exgcd(int &x,int &y,int a,int b){
	if(b==0){
		x=1;y=0;
		return a;
	}
	int gcd=exgcd(x,y,b,a%b);
	int z=x;
	x=y;y=z-(a/b)*y;
	return gcd;
}
signed main(){
	T=read();
	while(T--){
		p=read(),a=read(),b=read();
		b%=p;
		phi=p-1;
		work(phi);
		for(int i=1;i<=p;i++){
			bool flag=false;
			for(int j=1;j<=tot;j++)if(ksm(i,phi/prime[j])==1){flag=true;break;}
			if(flag==false){g=i;break;}
		}
		int r=BSGS(g,b);
		int gcd=exgcd(x,y,a,phi);
		if(r%gcd!=0){printf("No Solution\n");continue;}
		x=x*r/gcd;
		int k=phi/gcd;
		x=(x%k+k)%k;
		num=0;
		while(x<phi){ans[++num]=ksm(g,x),x+=k;}
		sort(ans+1,ans+1+num);
		for(int i=1;i<=num;i++)printf("%lld ",ans[i]);
		printf("\n");
	}
	return 0;
}

二次同余方程

p为素质数至于为什么要有素,接着看就知道了)
\(x^2\equiv A(mod\ p)\)

一些引理

引理1:x有正整数解的充要条件是\(A^{\frac{p-1}{2}} \equiv 1(mod \ p)\)
证明:
由费马小定理得

\[A^{p-1} \equiv 1(mod \ p) \]

\[A^{p-1}-1 \equiv 0(mod\ p) \]

\[(A^{\frac{p-1}{2}}+1)+(A^{\frac{p-1}{2}}-1) \equiv 0 \Rightarrow A^{\frac{p-1}{2}}\equiv \pm1(mod \ p) \]

\(x^2 \equiv A(mod\ p)\)\(A^{\frac{p-1}{2}} \equiv x^{p-1}(mod \ p)\)
由费马小定理

\[x^{p-1} \equiv 1(mod\ p) \]

\[A^{\frac{p-1}{2}}\equiv 1(mod \ p) \]

证毕。
推论:x无正整数解的时\(A^{\frac{p-1}{2}} \equiv -1(mod \ p)\)
引理2:一共只有\((p-1)/2\)(不考虑0)个数存在模\(p\)意义下的二次方根,对于其中的每个数都存在两个互为相反数的解。
证明:
假设有两个数u,v,\(u^2-v^2 \equiv 0(mod\ p)\)那么\((u+v)*(u-v) \equiv 0 (mod\ p)\),因为u,v\(\subset [1,p-1]\)(不考虑0),所以\(-p+1\leq u-v\leq p-1\)无法被\(p\)整除,所以\(u+v \equiv 0(mod\ p)\)
所以u,v互为相反数且对应唯一一个A,反过来也一样。因为不考虑0且p为奇数,这样的数有\(\frac{p-1}{2}\)对,且对应着\(\frac{p-1}{2}\)个A。
证毕。
此时我们就可以有一个解法。

解法一

1:我们求出\(p\)的一个原根\(g\)。然后求出以\(g\)为底的A关于模p的指数r,即\(g^r\equiv A(mod\ p)\)(离散对数,用BSGS求解)
2:\(A\)满足\(A^{\frac{p-1}{2}}\equiv1(mod\ p)\)带入得\(g^{\frac{r(p-1)}{2}}\equiv 1(mod\ p)\)由原根的定义得\(p-1 \mid \frac{r(p-1)}{2} \Rightarrow r \mid 2\)。然后\(g^{\frac{r}{2}}\)就是一个解,因为\(r\mid2\)所以可以直接快速幂,另一个解就是相反数。


又一些引理

定义:如果\(x^2 \equiv n (mod\ p)\)有解就说n是模p意义下的一个二次剩余,无解就说n是模p意义下的一个非二次剩余。
然后就是一个非常神的推导。
我们定义设a满足\(w=a^2-n\)是一个非二次剩余。
引理3\((a+\sqrt{w})^p \equiv a-\sqrt{w}(mod \ p)\)这实际上拓展了数域,可以用复数去理解。
证明:
\((a+\sqrt{w})^p\)用二项式定理展开为\(\sum_{j=0}^p C_p^ja^jw^{p-j}\)。因为p是一个质数所以当\(j!=0且j!=p\)\(C_p^j\)都为\(0\)否则为1,因为\(C_p^j\)分子中有\(p\)消不掉最后\(mod\ p\)得0。那么\((a+\sqrt{w})^p \equiv a^p+\sqrt{w}^p\equiv a^{p-1}*a+w^{\frac{p-1}{2}}*\sqrt{w}(mod\ p)\)由费马小定理和引理1的推论(别忘了w是一个非二次剩余)最后化简为\(a-\sqrt{w}\)

推导

\[(a+\sqrt{w})^{p+1}=(a+\sqrt{w})^{p}*(a+\sqrt{w}) \]

\[=(a-\sqrt{w})(a+\sqrt{w}) \]

\[=a^2-w \]

\[=a^2-(a^2-n) \]

\[=n \]

然后我们随机一个\(a\)满足条件,\(x\equiv (a+\sqrt{w})^{\frac{p+1}{2}}(mod\ p)\)根据拉格朗日定理可得答案中w的系数必然为0(然而我并不知道拉格朗日定理,所以模板拓展数域的乘法是背的),就可以解得\(x\)了,又因为引理2所以期望随机次数为2。复杂度可以保证。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstdlib>
#include<ctime>
using namespace std;
#define random(a,b) (rand()%(b-a+1)+a)
#define int long long
int n,p,w;
bool flag;
struct complex{
	int x,y;
	complex (int xx=0,int yy=0){
		x=xx;y=yy;
	}
};
complex operator *(complex a,complex b){
	return complex(((a.x*b.x%p+w*a.y%p*b.y%p)%p+p)%p,((a.x*b.y%p+a.y*b.x%p)%p+p)%p);
}
int read(){
	int sum=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
	return sum*f;
}
complex ksm(complex x,int b){
	complex tmp(1,0);
	while(b){
		if(b&1)tmp=tmp*x;
		b>>=1;
		x=x*x;
	}
	return tmp;
}
int ksm(int x,int b){
	int tmp=1;
	while(b){
		if(b&1)tmp=tmp*x%p;
		b>>=1;
		x=x*x%p;
	}
	return tmp;
}
int work(int n){
	if(p==2)return n;
	if(ksm(n,(p-1)/2)+1==p)flag=true;
	int a;
	while(233){
		a=random(0,p-1);
		w=((a*a-n)%p+p)%p;
		if(ksm(w,(p-1)/2)+1==p)break;
	}
	complex res(a,1);
	complex ans(0,0);
	ans=ksm(res,(p+1)/2);
	return ans.x;
}
signed main(){
	srand((unsigned)time(NULL));
	p=read();n=read();
	n%=p;
	int ans1=work(n);
	int ans2=p-ans1;
	if(flag){printf("No Solution\n");return 0;}
	if(ans1==ans2)printf("%lld\n",ans1);
	else printf("%lld %lld",min(ans1,ans2),max(ans1,ans2));
	return 0;
}
posted @ 2019-01-09 20:24  Xu-daxia  阅读(3034)  评论(1编辑  收藏  举报