数论难点选讲

Miller_Rabin素性测试

我们首先看这样一个很简单的问题:判定正整数\(n\)是否为素数

最简单的做法就是枚举\(2\)\(n\)的所有数,看是否有数是\(n\)的因数,时间复杂度\(O(n)\)

稍微优化一下发现只要枚举\(2\)\(\sqrt{n}\)中的数就可以了

然后发现数据范围\(n\leq 10^{18}\),时间复杂度直接就死掉了QAQ

我们就要考虑新的方法了

首先引入两个定理

1、费马小定理

如果\(p\)是素数,且\(gcd(a,p)=1\),那么\(a^{p-1}\equiv 1(mod \ n)\)

证明什么的你随便找本数论书自己翻一下

注意它的逆定理不一定成立(或者说是它的逆定理在大多数情况下都成立)

2、二次探测定理(其实这也没有一个准确的名字)

如果\(p\)是奇素数,\(x<p\),且\(x^2\equiv1(mod\ p)\),那么\(x=1\)\(x=p-1\)

证明:由同余式知\(x^2-1\equiv0(mod\ p)\),即\(p|(x+1)(x-1)\)

​ 又由\(p\)是素数知\(p|x-1\)\(p|x+1\),解得\(x=1\)\(x=p-1\)

诶等等zzr没事给证明干嘛?zzr不是最讨厌证明了吗

由上面很简单的证明过程我们可以发现,\(x=1\)\(x=p-1\)这两个解其实是对所有的\(p\)都成立的

即无论\(p\)取什么值\(x\)取上面两个值是一定可以的

但是当\(p\)是一个合数的时候,此时原同余方程的解\(x\)就不只上面这两个了,而是会有多个

换一句话说:如果上面的\(x\)取到了1和\(p-1\)以外的数,就说明\(p\)不是一个素数了

我们主要利用上面两个性质来进行素数判定

1、取\(2^q*m=n-1\)\(q,m\)均为正整数且\(m\)为奇数),同时任意取小于\(n\)的正整数\(a\)

2、求出\(a^{n-1}\text%n\),如果这个值不为1那么\(n\)一定是合数(利用费马小定理)

3、遍历\(i\),使得\(1\leq i \leq q\),如果\(2^i*m\text%n=1\)并且\(a^{i-1}*m\text%n!=1或n-1\),那么由二次探测定理就知道原同余方程出现一个特殊解,说明\(n\)不是一个素数

上面的方法有一个小问题:由于费马小定理的逆定理不一定成立(在大多数情况下成立),因此有时我们会对\(n\)进行误判,具体的,每做一次发生误判的概率是\(\frac{1}{4}\)

解决的方法在上面的解法中也有体现:换用不同的\(a\),多进行几次即可

好了上面就是完整的miller-rabin测试了

一道例题:poj3518Prime Gap

题意:两个相邻的素数的差值叫做Prime Gap。输入一个K,求K两端的素数之差,如果K本身是一个素数,输出0;

分析:其实数据很小你直接筛一下也可以

​ 或者你直接暴力寻找当前这个数相邻的数是否是质数,两端分别记录第一次找到的质数即可

#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<stdlib.h> 
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
using namespace std;
#define int long long
int n;

int read()
{
	int x=0,f=1;char ch=getchar();
	while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
	while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
	return x*f;
}

int mul(int x,int y,int n)
{	
	x%=n;y%=n;
	int ans=0,sum=x;
	while (y)
	{
		int tmp=y%2;y/=2;
		if (tmp) ans=(ans+sum)%n;
		sum=(sum+sum)%n;
	}
	return ans;
}

int qpow(int x,int y,int n)
{
	int ans=1,sum=x;
	while (y)
	{
		int tmp=y%2;y/=2;
		if (tmp) ans=mul(ans,sum,n);
		sum=mul(sum,sum,n);
	}
	return ans;
}

bool prime(int m,int q,int a,int n)
{
	int now=qpow(a,m,n);
	if ((now==1) || (now==n-1)) return 1;
	int i;
	for (i=1;i<=q;i++)
	{
		int x=mul(now,now,n);
		if ((x==1) && (now!=1) && (now!=n-1)) return 0;
		now=x;
	}
	if (now!=1) return 0;//其实这里是将费马小定理的检测放在了最后,省去再做一次快速幂
	return 1;
}

bool miller_rabin(int x)
{
	if (x==2) return 1;
	if ((x<2) || (x%2==0)) return 0;
	int num=x-1,tim=0;
	while ((num) && (num%2==0)) {num/=2;tim++;}
	//cout << num << " " <<tim << endl;
	int i;
	for (i=1;i<=10;i++)//一般都会进行20次左右,不过数据范围小对吧2333
	{
		int a=rand()%(x-1)+1;
		if (!prime(num,tim,a,x)) return 0;
	}
	return 1;
}

void work()
{
	if (miller_rabin(n)) {printf("0\n");return;}
	//cout <<1;
	int l=n-1,r=n+1;
	while (!miller_rabin(l)) l--;
	while (!miller_rabin(r)) r++;
	printf("%d\n",r-l);
}

signed main()
{
	n=read();
	while (n)
    {
		work();
		n=read();
	}
	return 0;
}

BSGS相关

BSGS

给定\(a,b,p\),求最小的\(x\)使得\(a^x\equiv b(mod\ p)\),保证\(p\)是质数

考虑这样的一个数\(m\),使得\(x=qm-r(q\in[1...p/m],r\in[0...m))\)

那么原式则为\(a^{qm-r}\equiv b(mod\ p)\),即\(a^{qm}\equiv b·a^r(mod\ p)\)

我们暴力枚举\(r\),将\(b·a^r\)的值存入一个map中

接着枚举\(q\),计算出对应的\(a^{qm}\),如果这个值在map中存在的话就直接输出结果,否则继续搜索直到无解

时间复杂度\(O(\frac{p}{m}·m)\),取\(m=\lceil \sqrt p \rceil\)时间复杂度最优,为\(O(\sqrt(p))\)

模板题:[TJOI2007]可爱的质数

#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<vector>
#include<queue>
#include<map>
using namespace std;
const int maxd=1000000007,N=100000;
const double pi=acos(-1.0);
typedef long long ll;
ll a,b,c;
map<ll,int> mp;

int read()
{
	int x=0,f=1;char ch=getchar();
	while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
	while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
	return x*f;
}

ll qpow(ll x,ll y)
{
	ll ans=1,sum=x;
	while (y)
	{
	    int tmp=y%2;y/=2;
	    if (tmp) ans=(ans*sum)%c;
	    sum=(sum*sum)%c;
	}
	return ans;
}

int main()
{
	c=read();a=read();b=read();
	int i,m=sqrt(c)+1;ll sum=b;
	for (i=0;i<m;i++)
	{
		mp[sum]=i;
		sum=(sum*a)%c;
	}
	ll tmp=qpow(a,m);sum=1;
	for (i=1;i<=m;i++)
	{
		sum=(sum*tmp)%c;
		if (mp.count(sum)) {printf("%d",i*m-mp[sum]);return 0;}
	}
	printf("no solution");
	return 0;
}

EXBSGS

还是上面那个问题,如果此时\(p\)不是质数呢?

上面的分块做法似乎就不是很可行了因为保证了\(gcd(a,p)=1\)所以对于不同的\(x\)\(a^x\)\(p\)的值两两不同

对于这个问题我们考虑转化为BSGS,我们在同余式两边同时除以\(gcd(a,p)\),记作\(d\)

注意此时如果\(d\nmid b\),那么就只有在\(y=1,x=0\)的情况下有解,其他情况均无解

就这样一直除下去,直到\(d=1\),时停止。这时你得到了一个\(d\)的序列\(d_1,d_2,\cdots,d_t\)

以及一个变形之后的同余式:\(a^{x-t}\frac{x^t}{\prod d_i}\equiv\frac{b}{\prod d_i}(mod\ \frac{p}{\prod d_i})\)

好像直接BSGS就可以了?

是的,但是要先特判掉\(x\in[0..t]\)的情况

模板题:SPOJ MOD - Power Modulo Inverted

#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<vector>
#include<queue>
#include<map>
using namespace std;
const int maxd=1000000007,N=100000;
const double pi=acos(-1.0);
typedef long long ll;
map<ll,int> mp;
ll a,b,p;

int read()
{
	int x=0,f=1;char ch=getchar();
	while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
	while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
	return x*f;
}

ll qpow(ll x,ll y)
{
	ll ans=1,sum=x;
	while (y)
	{
		int tmp=y%2;y/=2;
		if (tmp) ans=(ans*sum)%p;
		sum=(sum*sum)%p;
	}
	return ans;
}

ll gcd(ll x,ll y)
{
	if (!y) return x;else return gcd(y,x%y);
}

int main()
{
	while (1)
	{
		a=read();p=read();b=read();
		if ((!a) && (!p) && (!b)) break;
		a%=p;b%=p;
		if (b==1) {printf("0\n");continue;}
		ll d=gcd(a,p),k=1,t=0;bool ok=1;
		//cout << "d=" << d << endl;
		while (d!=1)
		{
		    if (b%d) {printf("No Solution\n");ok=0;break;} 
		    b/=d;p/=d;k=(k*a/d)%p;t++;
		    if (k==b) {printf("%d\n",t);ok=0;break;}
		    d=gcd(a,p);
		}
		if (ok)
		{
			mp.clear();int i;ll sum=b;
			ll m=sqrt(p)+1;
			for (i=0;i<m;i++) 
			{
				mp[sum]=i;
				//cout << sum << " ";
				sum=(sum*a)%p;
			}
			//cout << endl;
			//cout << t << endl;
			ll tmp=qpow(a,m);sum=k;
			for (i=1;i<=m;i++)
			{
				sum=(sum*tmp)%p;
				if (mp.count(sum)) {printf("%lld\n",m*i-mp[sum]+t);ok=0;break;}
			}
			if (ok) printf("No Solution\n");
	    }
	}
	return 0;
}

原根

记住:\(1004535809\)\(998244353\)的原根为\(3\)\(1000000007\)的原根为\(5\)

相关定义

阶:若\(gcd(a,p)=1\),则最小的正整数\(n\)使得\(a^n\equiv 1\)被称作\(a\)\(p\)的阶

因为\(a^{\varphi(p)}\equiv 1\),所以一定有\(a\)\(p\)的阶\(n|\varphi(p)\)

\(a\)\(p\)的阶为\(\varphi(p)\),那么\(a\)为模\(p\)的一个原根

性质

1、只有当\(a=2,4,p^a,2*p^a\)时,\(a\)具有原根

2、记模\(m\)的原根为\(g\),则\(g^i(0 \leq i\leq m-2)\)取遍\(1\)\(p-1\)

求法

暴力的想法是\(g\)\(2\)开始枚举,检验当前\(g\)\(p\)的阶是否为\(\varphi(p)\),检验阶的方法就是让\(i\)\(1\)取到\(p-1\),判断是否出现了\(g^i\equiv 1(mod\ p)\)

考虑优化,前面对\(g\)的枚举似乎不好优化,考虑检验

结论:若对\(\forall i|\varphi(p)\),都没有\(g^i\equiv1(mod\ p)\),那么\(\forall i,0\leq i\leq \varphi(p)\),均无\(g^i\equiv 1(mod\ p)\)\(i\neq \varphi(p)\)

证明:首先有\(g^{\varphi(p)}\equiv 1(mod\ p)\)

考虑反证,假设存在一个最小的数\(c\),满足\(g^c\equiv 1(mod\ p)\),那么有条件值\(c<\varphi(p)\)\(c\)不是\(\varphi(p)\)的因数)

\(d=\varphi(p)-c\),那么有\(g^d\equiv 1(mod\ p)\),由假设知\(d>c\)

那么我们又可以推出\(g^{d-c}\equiv 1(mod\ p)\)

由更相减损术知\(g^{gcd(c,d)}\equiv 1(mod\ p)\),因为\(gcd(c,d)\leq c\),又由假设知\(gcd(c,d)=c\),也就是\(c|d\)

\(d=kc(d\in N_+)\),则\(\varphi(p)=c+d=(k+1)c\),推得\(c|\varphi(p)\),与条件矛盾,QED

于是我们在验证\(g\)的时候只需要枚举\(\varphi(p)\)的因数进行判断即可

时间已经很优秀了,但是我们还可以继续优化

\(\varphi(p)\)分解质因数\(\varphi(p)=p_1^{\alpha_1}p_2^{\alpha_2}\cdots p_m^{\alpha_m}\),于是只需要检验\(g^{\frac{\varphi(p)}{p_i}}mod\ p\)即可

因为此时我们用来检验的幂指数都是\(\varphi(p)\)中小于\(p\)的因数,它至少比\(p\)少了一个质因子,并且\(1\)的任何正整数次幂都是\(1\)

于是就有了下面一份代码(模板:51nod1135)

#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<vector>
#include<queue>
#include<map>
#include<set>
using namespace std;
#define lowbit(x) (x)&(-x)
#define rep(i,a,b) for (int i=a;i<=b;i++)
#define per(i,a,b) for (int i=a;i>=b;i--)
#define maxd 1000000007
typedef long long ll;
const int N=100000;
const double pi=acos(-1.0);
ll m,cnt=0,q[1001000];

int read()
{
	int x=0,f=1;char ch=getchar();
	while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
	while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
	return x*f;
}

ll qpow(ll x,ll y,ll p)
{
	ll ans=1;
	while (y)
	{
		if (y&1) ans=(ans*x)%p;
		x=(x*x)%p;
		y>>=1;
	}
	return ans;
}

ll get_phi(ll x)
{
	ll ans=x,i;
	for (i=2;i*i<=x;i++)
	{
		if (x%i) continue;
		ans=ans/i*(i-1);
		while (x%i==0) x/=i;
	}
	if (x>1) ans=ans/x*(x-1);
	return ans;
}

ll get_g(ll m)
{
	ll i,phi=get_phi(m);
	cnt=0;ll rest=phi;
	for (i=2;i*i<=m;i++)
	{
		if (phi%i) continue;
		q[++cnt]=phi/i;
		while (rest%i==0) rest/=i;
	}
	if (rest>1) q[++cnt]=phi/rest;
	ll g=2;
	while (1)
	{
		if (qpow(g,phi,m)!=1) {g++;continue;}
		bool flag=1;
		rep(i,1,cnt)
			if (qpow(g,q[i],m)==1) {g++;flag=0;break;}
		if (flag) return g;
	}
}

int main()
{
	m=read();
	printf("%lld",get_g(m));
	return 0;
}

作用

化乘法为加法,从而改变转移方程的形式,便于使用基于加法转移的优化操作

比如矩阵快速幂(CF1106F),FFT(SDOI序列统计)

中国剩余定理(CRT)

普通型

求解符合下列同余方程式的最小的\(n\)

\[\begin{cases} n\equiv a_1(mod\ b_1)\\ n\equiv a_2(mod\ b_2)\\ \cdots\\ n\equiv a_m(mod\ b_m) \end{cases} \]

其中满足\(gcd(b_i,b_j)=1,\forall i,j ,i\neq j\)

方法:

1)记\(B=b_1b_2\cdots b_m\)

2)求解\(m\)个同余方程组\(\frac{B}{b_i}t_i\equiv1(mod\ b_i)\)

3)\(ans=\sum_{i=1}^m\frac{B}{b_i}t_ia_i\),同时\(ans-=B*k\)使得\(k\)最大且\(ans\)不为负

证明(假的):

对于第\(i\)个同余方程组,因为\(b_j|B\)\(gcd(b_i,b_j)=1\),所以这一组同余方程得到的解不会影响到其它方程的解,

又因为最后减去的数是所有的\(b\)的倍数,所以也不会影响到同余式的性质

代码luogu 3868

#include<iostream>
#include<string.h>
#include<string>
#include<stdio.h>
#include<algorithm>
#include<math.h>
#include<vector>
#include<queue>
#include<map>
#include<set>
using namespace std;
#define lowbit(x) (x)&(-x)
#define sqr(x) (x)*(x)
#define fir first
#define sec second
#define rep(i,a,b) for (register int i=a;i<=b;i++)
#define per(i,a,b) for (register int i=a;i>=b;i--)
#define maxd 1000000007
#define eps 1e-6
typedef long long ll;
const int N=100000;
const double pi=acos(-1.0);
int n;
ll a[20],b[20],x[20],lcm;

ll read()
{
	ll x=0,f=1;char ch=getchar();
	while ((ch<'0') || (ch>'9')) {if (ch=='-') f=-1;ch=getchar();}
	while ((ch>='0') && (ch<='9')) {x=x*10+(ch-'0');ch=getchar();}
	return x*f;
}

void exgcd(ll a,ll b,ll &x,ll &y)
{
	if (b==0) {x=1;y=0;return;}
	else
	{
		exgcd(b,a%b,x,y);
		ll tmpx=x,tmpy=y;
		x=tmpy;y=tmpx-a/b*tmpy;
	}
}

ll mul(ll x,ll y)
{
	ll ans=0;
	while (y)
	{
		if (y&1) ans=(ans+x)%lcm;
		x=(x+x)%lcm;
		y>>=1;
	}
	return ans;
}

int main()
{
	n=read();
	rep(i,1,n) a[i]=read();
	rep(i,1,n) b[i]=read();
	rep(i,1,n) a[i]=(a[i]%b[i]+b[i])%b[i];
	lcm=1;ll ans=0,y;
	rep(i,1,n) lcm*=b[i];
	rep(i,1,n)
	{
		exgcd(lcm/b[i],b[i],x[i],y);
		x[i]=(x[i]%b[i]+b[i])%b[i];
		ans=(ans+mul(mul(lcm/b[i],x[i]),a[i]))%lcm;
	}
	ans=(ans+lcm)%lcm;
	printf("%lld",ans);
	return 0;
}

扩展型

非常抱歉,这篇文章鸽了

posted @ 2019-05-10 01:14  EncodeTalker  阅读(309)  评论(0编辑  收藏  举报