Processing math: 100%

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

写在前面

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

正文

高次同余方程

首先需要知道原根

定义

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

条件

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

性质

1<a>∣φ(p)
证明:
因为aφ(p)1(mod p)a<a>1(mod p)
所以aφ(p)<a>1(mod p)
进而得出aφ(p) mod <a>1(mod p)
假设<a>不是φ(p)的约数。
φ(p) mod<a>≠0
(φ(p) mod<a>) < (<a>)<a>的最小性矛盾,与假设矛盾原命题成立。
证毕。

2:设a关于模p的阶为<a>a0a1a2,...,a<a>1两两不同
证明:
假设ax1(mod p)ay1(mod p),且axay(mod p)x<y<<a>
ayx1(mod p)yx<<a>与阶的最小性矛盾,假设不成立,原命题成立。
证毕。

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

原根

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

定义

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

性质

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


那么如何找原根?

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


解高次同余方程的步骤

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

有些值得注意的点:

因为原根的性质1求出的答案没有重复。
为什么要用原根代换b,别的数不行吗?我们需要用原根表示b。见原根的性质1
因为要求原根且p为质数所以试用的p的范围很小,见原根的性质3
只要求求出通解的复杂度为O(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为素质数至于为什么要有素,接着看就知道了)
x2A(mod p)

一些引理

引理1:x有正整数解的充要条件是Ap121(mod p)
证明:
由费马小定理得

Ap11(mod p)

Ap110(mod p)

(Ap12+1)+(Ap121)0Ap12±1(mod p)

x2A(mod p)Ap12xp1(mod p)
由费马小定理

xp11(mod p)

Ap121(mod p)

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

解法一

1:我们求出p的一个原根g。然后求出以g为底的A关于模p的指数r,即grA(mod p)(离散对数,用BSGS求解)
2:A满足Ap121(mod p)带入得gr(p1)21(mod p)由原根的定义得p1r(p1)2r2。然后gr2就是一个解,因为r2所以可以直接快速幂,另一个解就是相反数。


又一些引理

定义:如果x2n(mod p)有解就说n是模p意义下的一个二次剩余,无解就说n是模p意义下的一个非二次剩余。
然后就是一个非常神的推导。
我们定义设a满足w=a2n是一个非二次剩余。
引理3(a+w)paw(mod p)这实际上拓展了数域,可以用复数去理解。
证明:
(a+w)p用二项式定理展开为pj=0Cjpajwpj。因为p是一个质数所以当j!=0j!=pCjp都为0否则为1,因为Cjp分子中有p消不掉最后mod p得0。那么(a+w)pap+wpap1a+wp12w(mod p)由费马小定理和引理1的推论(别忘了w是一个非二次剩余)最后化简为aw

推导

(a+w)p+1=(a+w)p(a+w)

=(aw)(a+w)

=a2w

=a2(a2n)

=n

然后我们随机一个a满足条件,x(a+w)p+12(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 @   Xu-daxia  阅读(3139)  评论(1编辑  收藏  举报
编辑推荐:
· 用 C# 插值字符串处理器写一个 sscanf
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
· .NET Core内存结构体系(Windows环境)底层原理浅谈
· C# 深度学习:对抗生成网络(GAN)训练头像生成模型
阅读排行:
· 趁着过年的时候手搓了一个低代码框架
· 本地部署DeepSeek后,没有好看的交互界面怎么行!
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 用 C# 插值字符串处理器写一个 sscanf
· 乌龟冬眠箱湿度监控系统和AI辅助建议功能的实现
点击右上角即可分享
微信分享提示