目录
- 第一章 勾股数组
- 第二章 勾股数组与单位圆
- 第三章 高次幂之和与费马大定理
- 第四章 整除性与最大公因式
- 第五章 线性方程和最大公因数
- 第六章 因式分解与算术基本定理
- 第七章 同余式
- 第八章 威尔逊定理
- 第九章 同余式、幂与费马小定理
- 第十章 同余式、幂与欧拉公式
- 第十一章 乘法逆元
- 第十二章 欧拉函数与中国剩余定理
- 第十三章 素数
- 第十四章 素数的计数
- 第十五章 梅森素数与完全数
- 第十六章 幂模m与逐次平方法
- 第十七章 快速乘
- 第十八章 计算模m的k次根
- 第十九章 素数测试与卡米歇尔数
- 第二十章 模p平方剩余
- 第二十一章 离散对数
- 第二十二章 佩尔方程
- 第二十三章 阶与原根
- 第二十四章 整除性质
- 第二十五章 数论函数之加性函数
- 第二十六章 数论函数之积性函数
- 第二十七章 迪利克雷卷积
- 第二十八章 莫比乌斯反演
- 第二十九章 杜教筛
- 第三十章 min_25筛
第一章 勾股数组
本原勾股数组
本原勾股数组是一个三元组(a,b,c,),其中a,b,c没有公因数,且满足a2+b2=c2。
勾股数组定理
每个本原勾股数组(a,b,c)其中(a为奇数,b为偶数)都可以从以下公式得出:a=st,b=(s2-t2)/2,c=(s2+t2)/2,其中s t 1。
定理一
a和b奇偶不同,且c为奇数。
证明:
假设a和b都是偶数,则c一定为偶数,于是a,b,c存在最小公因式2,不满足本原勾股数a,b,c没有公因式的条件,假设不成立。
假设a,b都为奇数,则c一定为偶数,于是设a=2x+1,b=2y+1,c=2z,∵a2+b2=c2
4x2+4x+1+4y2+4y+1=4z2
2x2+2x+2y2+2y+1=2z2,而奇数不可能等于偶数,假设不成立。
故a,b奇偶不同,且c一定为奇数。
定理二
a为奇数,b为偶数,则c+b和c-b都是完全平方数。
证明:
设正整数d为c+b,c-b的公因数,则d整除c-b和c+b,d也整除(c+b)+(c-b)=2c和(c+b)-(c-b)=2b,由于b,c互素,因此d等于1或2,但d整除
(c-b)(c+b)=a2,由于a为奇数,所以d不能等于2,因此d等于1,因此c+b和c-b无公因数,由于(c-b)(c+b)=a2,a2为平方数,且(c+b)与(c-b)互素,因此二者均为平方数。
推论
由定理二,不妨设c-b=t2,c+b=s2,则可以解出a=st,b=
,c=
,其中s
t
1。。
若优势题目给出a,可令t=1,s=a,则可直接推出b和c。
第二章 勾股数组与单位圆
由上一张勾股数组a2+b2=c2,两边同时除以c2,可得(a/c)2+(b/c)2=1,其形式上近似与单位圆的方程,x2+y2=1,据勾股数组定理,可以得出任何圆上的可行点都可以表示为x=
,y=
,令m=s/t,,由于x和y的实际含义被扩展到了实数域上,因此m的范围也就变成了
(-∞,∞),圆x2+y2=1上坐标是有理数的店都可以由公式
(x,y)=
,
)表示,其中m为有理数((-1,0)除外,此为m趋于∞是的极限值)。
第三章 高次幂之和与费马大定理
费马大定理:n 3时,方程xn+yn=zn没有正整数解。
第四章 整除性与最大公因式
假设m与n是整数,且m不等于0,m整除n是指n是m的倍数,即存在整数k是的n=mk,记作m|n。
欧几里得算法
通过递推求a与b的最大公因数,记为gcd(a,b)。
typedef long long ll;
ll gcd(ll a,ll b){
return b?gcd(b,a%b):a;
}
第五章 线性方程和最大公因数
裴蜀定理
对于a,b,存在x,y使得ax+by=gcd(a,b)。
扩展欧几里得
求解gcd的同时求解方程ax+by=gcd(a,b)的一个解( , ,复杂度O(log(b))。
ll exgcd(ll a,ll b,ll &x,ll &y){
if(b==0){
y=0;
x=1;
return a;
}
ll r=exgcd(b,a%b,x,y);
ll temp=y;
y=x-(a/b)*y;
x=temp;
return r;
}
线性方程定理
设a与b是非0的整数,ax+by=gcd(a,b),总有一个解( , )且这个解可以通过扩展欧几里得算法得到,且方程的每一个解就可以表示为( +k , -k ),其中k为任意整数。
第六章 因式分解与算术基本定理
算术基本定理(唯一分解定理)
每个整数n 2可唯一分解成素数成积n= … 。即整数n可以以某种方式分解成素数乘积,且仅有一种这样的分解。
质因数分解
朴素分解
复杂度O( )
map<ll,ll>prime_factor(ll n){
map<ll,ll>res;
for(ll i=2;i*i<=n;i++){
while(n%i==0){
++res[i];
n/=i;
}
}
if(n!=1)res[n]=1;
return res;
}
pollard_rho算法
传说中的随机算法,复杂度O(
),适用于n较大的情况,判断一个大整数是否为素数用Miller_rabin算法,求一个大整数的所有质因数用Pollard_rho算法。
poj1181:求一个整数是否是素数,如果不是,则输出它最小的质因数。
#include<iostream>
#include<cmath>
#include<ctime>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
int t;
ll n,prim[1000];
ll ans;
int tot;
ll mul_mod(ll a,ll b,ll c)
{
ll t=0;
a%=c;
b%=c;
while(b){
if(b&1){
t=(t+a)%c;
}
a<<=1;
a%=c;
b>>=1;
}
return t;
}
//ll pow_mod(ll a,ll b,ll c)
//{
// ll t=1;
// while(b){
// if(b&1) t=(t*a)%c;
// b>>=1;
// a=(a*a)%c;
// }
// return t;
//}
ll pow_mod(ll x,ll n,ll mod){
if (n==1) return x%mod;
int bit[90],k=0;
while (n){
bit[k++]=n&1;
n>>=1;
}
ll ret=1;
for (k=k-1;k>=0;k--){
ret=mul_mod(ret,ret,mod);
if (bit[k]==1) ret=mul_mod(ret,x,mod);
}
return ret;
}
bool check(ll a,ll n,ll x,ll t)
{
ll ret=pow_mod(a,x,n);
ll tmp=ret;
for(int i=0;i<t;i++){
ret=mul_mod(ret,ret,n);
if(ret==1&&tmp!=1&&tmp!=n-1) return true;
tmp=ret;
}
if(ret!=1) return true;
return false;
}
bool Miller_rabin(ll n)
{
ll x=n-1,t=0;
while((x&1)==0){
x>>=1;
t++;
}
int ok=1;
if(t>=1&&(x&1)){
for(int i=0;i<20;i++){
ll a=rand()%(n-1)+1;
if(check(a,n,x,t)){
ok=1;break;
}
ok=0;
}
}
if(!ok||n==2) return false;
return true;
}
//ll gcd(ll a,ll b)
//{
// if(b==0) return a;
// return gcd(b,a%b);
//}
ll gcd(ll a,ll b){
if (a==0) return 1;
if (a<0) return gcd(-a,b);
while (b){
ll t=a%b; a=b; b=t;
}
return a;
}
ll Pollard_rho(ll x,ll c)
{
ll i=1,k=2;
ll x0=rand()%x,y=x0;
while(1){
i++;
x0=(mul_mod(x0,x0,x)+c)%x;
ll d=gcd(y-x0,x);
if(d>1&&d<x) return d;
if(y==x0) return x;
if(i==k){
y=x0;
k+=k;
}
}
}
void findfac(ll n)
{
if(!Miller_rabin(n)){
prim[tot++]=n;
return;
}
ll p=n;
while(p>=n) p=Pollard_rho(p,rand()%(n-1)+1);
findfac(p);
findfac(n/p);
}
int main()
{
srand(time(NULL));
scanf("%d",&t);
while(t--){
scanf("%lld",&n);
if(!Miller_rabin(n)){
printf("Prime\n");
continue;
}
tot=0;
findfac(n);
ans=prim[0];
for(int i=1;i<tot;i++){
ans=min(ans,prim[i]);
}
printf("%lld\n",ans);
}
}
第七章 同余式
如果m整除a-b,则称a-b模m同余,记为a
b(mod m)
其中m记为同余式的模,具有相同模的同余式,在许多方面表现得像通常的等式,其满足同余式的加减乘法,如
(mod m)且
(mod m),
则
(mod m),且
(mod m)
但是同余式除法仅在除数与模数互素时成立,即ac
bc(mod m)仅在gcd(c,m)=1时成立。
线性同余定理
求解同余式:设a,c与m均是整数,m
1,并设g=gcd(a,m)
如果g
c,则同余式ax
c(mod m)无解;
如果g|c恰好有g个不同的解,要求这些解则先要求线性方程au+mv=g的一个解(通过扩展欧几里得算法)(
,
),则
=
是该同余式的一个解,
而解的完全集为x
+k
(mod m) k=0,1,2,3,…,g-1。
模p多项式根定理
设p为素数,f(x)= xd+ xd-1…= 是次数为d 1的整系数多项式,则p ,则同余式f(x)$\equiv$0(mod p)最多又d个模p不同余的解。
第八章 威尔逊定理
当且仅当p为素数时,p|(p-1)!+1。
其逆反命题为当p是合数,则(p-1)!
-1(mod p)。
第九章 同余式、幂与费马小定理
费马小定理
设p为素数,a是任意整数,且a 0(mod p)(即p a),则有ap-1 1(mod p)
第十章 同余式、幂与欧拉公式
欧拉公式
如果gcd(a,m)=1则
1(mod m),其中
为欧拉函数,意指区间[1,m]之间与m互质的数的个数。
第十一章 乘法逆元
逆元的意义
对于一些题目会要求把结果模一个数,通常是一个较大的质数,对于加减乘法通过同余定理可以直接拆开计算,但对于(a/b)%mod这个式子,是不可以写成 的,但是可以写成(a+b-1) mod,其中b-1表示b的逆元。
费马小定理求逆元
由费马小定理可知,当p为质数,且q
a时有ap-1
1(mod p)分解后就可以写成a
ap-2
1(mod p),故可知当p为质数,a,p互质时a关于p的逆元就是ap-2,因此通过快速幂求解即可,复杂度O(log(mod))。
费马小定理求逆元模板
ll qkpow(ll a,ll b,ll mod){
ll t=1,tt=a%mod;
while(p){
if(p&1)t=t*tt%mod;
tt=tt*tt%mod;
p>>=1;
}
return t;
}
ll getinv(ll a,ll mod){
return qkpow(a,mod-2,mod);
}
欧拉公式求逆元
与费马小定理同理,当a,p互质,但p不一定为质数时,需要采用欧拉公式使得a-1为 ,在模板中将mod-2改为 即可,时间复杂度O(log( (mod)))。
扩展欧几里得算法求逆元
当所给的数与模数不互素时,我们可以选择解线性方程的方式来解出一个数对应模数的逆元。
形如a
x
1(mod p)我们可以转化为ax+py=1,通过扩展欧几里得算法求出x即为对应p时的逆元,复杂度O(log(a))。
扩展欧几里得算法求逆元模板
ll exgcd(ll a,ll b,ll &x,ll &y){
if(b==0){
x=1,y=0;
return a;
}
ll ret==exgcd(b,a%b,y,x);
y-=a/b*x;
return ret;
}
ll getinv(ll a,ll mod){
ll x,y;
ll d=exgcd(a,mod,x,y);
return d==1?(x%mod+mod)%mod:-1;
}
递推法求逆元
即打表求逆元,令模数为p,原数为i,令t=p/i,k=p%i,则有
t * i+k
0(mod p)
即-t * i
k(mod p),当i为p的因数时,逆元不存在,不讨论,当i不是p的因数时自然有gcd(ik,p)=1,故可对原式做除法
-t
inv[k]
inv[i](mod p)
将k,t重新代回,则有inv[i]=(p-p/i)*inv[p%i]%p
可在O(n)的时间内推出逆元。
递推法求逆元模板
ll inv[mod+5]
void getinv(ll mod){
inv[1]=1;
for(ll i=2;i<mod;i++)
inv[i]=(mod-mod/i)*inv[mod%i]%mod;
}
第十二章 欧拉函数与中国剩余定理
欧拉函数
φ(m)为欧拉函数,意指区间[1,m]之间与m互质的数的个数。
函数公式
欧拉函数筛
#include<iostream>
#include<cstdio>
#define N 40000
using namespace std;
int n;
int phi[N+10],prime[N+10],tot,ans;
bool mark[N+10];
void getphi()
{
int i,j;
phi[1]=1;
for(i=2;i<=N;i++)//相当于分解质因式的逆过程
{
if(!mark[i])
{
prime[++tot]=i;//筛素数的时候首先会判断i是否是素数。
phi[i]=i-1;//当 i 是素数时 phi[i]=i-1
}
for(j=1;j<=tot;j++)
{
if(i*prime[j]>N) break;
mark[i*prime[j]]=1;//确定i*prime[j]不是素数
if(i%prime[j]==0)//接着我们会看prime[j]是否是i的约数
{
phi[i*prime[j]]=phi[i]*prime[j];break;
}
else phi[i*prime[j]]=phi[i]*(prime[j]-1);//其实这里prime[j]-1就是phi[prime[j]],利用了欧拉函数的积性
}
}
}
int main()
{
getphi();
}
求单个数的欧拉函数值
直接求小于或等于n,且与n互质的个数,时间复杂度O(log(n))。
ll oula(ll n){
ll res=n;
for(ll i=2;i*i<=n;i++)
if(n%i==0){
res=res-res/i;
do
n/=i;
while(n%i==0);
}
if(n>1)res=res-res/n;
return res;
}
中国剩余定理
设m和n是整数,gcd(m,n)=1,b与c为任意整数,则同余数组
x
b(mod m)与x
c(mod n)恰好有一个解0
x
mn。
中国剩余定理解同余方程组
其中
,
…
为两两互质的整数,求x的最小非负整数解。
解:
令M=
,由于
互质,故M为所有
的最小公倍数,令
为同余方程
0(mod
)的解,由于
k
i均有
(mod
)
故x有解x=
故又通解x+i*M(i
Z)故其最小非负整数解就是(x%M+M)%M
其中
即是
在模数为
时的逆元,故用扩展欧几里得算法算出
再做累加即可得出答案。
void exgcd(ll a,ll,b,ll &x,ll &y){
if(b==0){x=1;y=0;return;}
exgcd(b,a%b,x,y);
ll tp=x;
x=y;
y=tp-a/b*y;
}
ll china(){
ll ans=0,lcm=1,x,y;
for(ll i=1;i<=k;i++)lcm*=b[i];
for(ll i=1;i<=k;i++){
ll tp=lcm/b[i];
exgcd(tp,b[i],x,y);
x=(x%b[i]+b[i])%b[i];//x要为最小非负整数解
ans=(ans+tp*x*a[i])%lcm;
}
return (ans+lcm)%lcm;
}
扩展中国剩余定理
前面已经解决了,当
互素时线性同余方程的解,那么当
不互素时呢?
如何解余数不互质的线性同余方程组:
首先对起那面两个式子进行处理,我们可将同余式化成
,据前面的扩展欧几里得可知此方程在gcd(
时有解,且可以解出
同时满足一式和二式,此时可以得到一个同时满足上面两个式子的X=
,我们取X=
为代表构造一个数d满足
&&
,这样就可以用同余式x
(mod d)来代替原来两个式子,由此,令d=lcm(
)即可。
据上,则可以将原式合并成下式
x
(mod lcm(
))。
据此通过迭代即可将所有的等式合并成一个式子得出最终答案。
ll exchina(){
llM=m[1],A=a[i],t,d,x,y;
ll i;
for(i=2;i<=n;i++){
d=exgcd(M,m[i],x,y);
if((a[i]-A)%d)return -1;
x*=(a[i]-A)/d,t=m[i]/d,x=(x%t+t)%t;
A=M*x+A,M=M/d*m[i],A%=M;
}
第十三章 素数
无穷多素数定理
存在无穷多的素数。
模四余三素数定理
存在无数多的模四余三的素数。
算数级数的迪利克雷定理
设a与m为整数,gcd(a,m)=1,则存在无数多的素数模m余a,即存在无数多的素数满足p a(mod m)。
第十四章 素数的计数
素数定理
当x很大时,小于x的素数个数趋近于 ,即 =1。
哥德巴赫猜想
强哥德巴赫猜想:任一大于2的偶数都可写成两个素数之和。
弱哥德巴赫猜想:任意大于7的奇数都可写成三个素数之和。
孪生素数猜想
存在无数多的素数p使得p+2也是素数,存在无数多的素数p使得p+2为素数或两个素数的乘积。
N^2+1猜想
有无穷多个形如N2+1的素数。
第十五章 梅森素数与完全数
梅森素数
对于整数 与 ,若an-1是素数,则a比等于2,且n必为素数。我们将形如2p-1的素数成为梅森素数。
完全数
因数的和与原本的数相等,则称这个数为完全数。
欧几里得完全数公式
若2p-1为素数,则2p-1(2p-1)为完全数。
σ函数
(x)=n的所有因数之和,则其有公式
欧拉完全数定理
如果n为偶完全数,则n形如2p-1(2p-1),其中2p-1为梅森素数。
第十六章 幂模m与逐次平方法
快速幂
ll pow(ll a,ll n,ll p){
ll ans=1;
while(n){
if(n&1)
ans=ans*a%p;
a=a*a%p;
n>>=1;
}
return ans;
}
字符串读取
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
ll quick_mod(ll a,ll b){
ll ans=1;
while(b){
if(b&1){
ans=(ans*a)%mod;
b--;
}
b/=2;
a=a*a%mod;
}
return ans;
}
ll quickmod(ll a,char *b,ll len){
ll ans=1;
while(len>0){
if(b[len-1]!='0'){
ll s=b[len-1]-'0';
ans=ans*quick_mod(a,s)%mod;
}
a=quick_mod(a,10)%mod;
len--;
}
return ans;
}
int main(){
char s[100050];
ll a;
while(~scanf("%lld",&a)){
scanf("%s",s);
ll len=strlen(s);
printf("%lld\n",quickmod(a,s,len));
}
return 0;
}
欧拉降幂公式
当B (C)时有:AB C= C
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
char a[1000006];
ll x,z;
ll quickpow(ll x,ll y,ll z){
ll ans=1;
while(y){
if(y&1){
ans=(ans*x)%z;
}
x=x*x%z;
y>>=1;
}
return ans;
}
ll phi(ll n){
ll i,res=0;
for(i=2;i*i<=n;i++){
if(n%i==0){
res=res-res/i;
while(n%i==0)
n/=i;
}
}
if(n>1)
res=res-res/n;
return res;
}
int main(){
while(~scanf("%lld %s %lld",&x,a,&z)){
ll len=strlen(a);
ll p=phi(z);
ll ans=0;
for(ll i=0;i<len;i++)
ans=(ans*10+a[i]-'0')%p;
ans+=p;
printf("%lld\n",quickpow(x,ans,z));
}
return 0;
}
第十七章 快速乘
快速计算a*b%mod的结果,对于大数直接乘可能会爆long long,用快速乘每一步都取余不会爆掉。
ll q_mul(ll a,ll b,ll mod){
ll ans=0;
while(b){
if(b&1)ans=(ans+a)%mod;
a=a(a<<1)%mod;
b>>=1;
}
return ans;
}
第十八章 计算模m的k次根
设b,k,m为已知整数,满足gcd(b,m)=1且gcd(k,
(m))=1,则可以通过以下步骤得出同余式
(mod m)
1.计算
(m);
2.求满足ku-
(m)v=1的正整数u与v;
3.x=bu(mod m)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
char a[1000006];
ll x,z;
ll quickpow(ll x,ll y,ll z){
ll ans=1;
while(y){
if(y&1){
ans=(ans*x)%z;
}
x=x*x%z;
y>>=1;
}
return ans;
}
ll exgcd(ll a,ll b,ll &x,ll &y){
if(b==0)x=1,y=0;
else{
exgcd(b,a%b,y,x);
y-=a/b*x;
}
}
ll phi(ll n){
ll i,res=0;
for(i=2;i*i<=n;i++){
if(n%i==0){
res=res-res/i;
while(n%i==0)
n/=i;
}
}
if(n>1)
res=res-res/n;
return res;
}
int main(){
ll x,k,b,m;
ll xx,yy;
while(cin>>k>>b>>m){
ll phim=phi(m);
exgcd(k,phim,xx,yy);
xx=(xx%phim+phim)%phim;
cout<<xx<<endl;
x=quickpow(b,xx,m);
cout<<x<<endl;
cout<<quickpow(x,k,m)<<endl;
}
return 0;
}
第十九章 素数测试与卡米歇尔数
根据费马小定理,假如p为素数,那么对所有的整数a都有ap a(mod p),则可以通过不断选取a,若出现不满足这个条件,则这个数就是合数,但是存在一种卡米歇尔数,其虽然为合数,但是任意的a都满足上述同余式。
卡米歇尔数
卡米歇尔数是可以冒充素数的一种合数,其没有任何可以用费马测试法判断出为合数的证据。
素数的一个性质
设p为一个奇素数,记p-1=2kq,q为奇数,
设a为不被p整除的整数,则下述的两个条件之一成立:
1.aq%p=1;
2.数aq,a2q,
,…,
之一模p余-1。
合数的拉宾-米勒测试
素数的一个性质
设p为一个奇素数,记p-1=2kq,q为奇数,
设a为不被p整除的整数,则下述的两个条件之一成立:
1.aq%p=1;
2.数aq,a2q,
,…,
之一模p余-1。
设p为奇素数,记p-1=2kq,q为奇数,对不被p整除的某个a,若上述两个条件都不满足,则p为合数。
ll prime[6]={2,3,5,233,331};
ll qmul(ll x,ll y,ll mod){
ll res=1;
while(n){
if(n&1)res=qmul(res,a,mod);
a=qmul(a,a,mod);
a>>=1;
}
return res;
}
bool miller_rabin(ll p){
if(p<2)return 0;
if(p!=2&&p%2==0)return 0;
ll s=p-1;
while(!(s&1))s>>=1;
for(ll i=0;i<5;i++){
if(p==prime[i])return 1;
ll t=s,m=qpow(prime[i],s,p);
while(t!=p-1&&m!=1&&m!=p-1){
m=qmul(m,m,p);
t<<=1;
}
if(m!=p-1&&!(t&1))return 0;
}
return 1;
}
素数筛法
埃氏筛法
bool prime[N];
void init(){
for(ll i=2;i<N;i++)prime[i]=true;
for(ll i=2;i<N;i++){
if(prime[i]){
for(ll j=i*2;j<N;j+=i){
prime[j]=false;
}
}
}
}
线性筛法
bool prime[N];
ll p[N],tot;
void init(){
for(ll i=2;i<N;i++)prime[i]=true;
for(ll i=2;i<N;i++){
if(prime[i])p[tot++]=i;
for(ll j=0;j<tot&&i*p[j]<N;j++){
prime[i*p[j]]=false;
if(i%p[j]==0)break;
}
}
}
第二十章 模p平方剩余
二次剩余与二次非剩余
与一个平方数模p同余的,不是p的倍数的数称为模p的二次剩余,不与任何一个平方数模p同余的数称为模p的二次非剩余,我们将二次剩余记作QR,二次非剩余记作NR。
定理:设p为一奇素数,则恰有
个模p的二次剩余,且恰有
个模p的二次非剩余。
二次剩余乘法法则
勒让德符号:勒让德发现QR和NR的乘法性质同+1和-1很像,因此他引出了如下符号:
(
)=
设p为奇素数,则
(
)*(
)=(
)。
欧拉准则
设p为奇素数,则
(mod p),其中
为勒让德符号。
二次剩余Cipolla算法
在介绍CIpolla算法之前我们需要先引入一些定理或引理,算法的用处是解x2
n(mod p)因此下面所称的方程均为左式,下面的引理或定理中,所有p均为奇素数。
引理一:n(p-1)/2
1(mod p)。
证明:即欧拉准则,一个数或为二次剩余数,或为二次非剩余数。
引理二:方程x2
n(mod p)有解当且仅当n(p-1)/2
1(mod p)
证明:方程有解则n为二次剩余。
引理三:设a满足
=a2-n不是模p的二次剩余,即x2
(mod p)无解,则x
是方程的解。
#include<iostream>
#include<cstdlib>
using namespace std;
typedef long long LL;
struct pii{
int x,y;
pii(int x=0,int y=0):x(x),y(y){}
};
LL w;
pii multi(pii a,pii b,LL p)
{
return pii((a.x*b.x%p+a.y*b.y%p*w%p)%p,(a.x*b.y%p+a.y*b.x%p)%p);
}
LL quick_pow_D(pii a,LL b,LL p)
{
pii ans(1,0);
while(b)
{
if(b&1) ans=multi(ans,a,p),b--;
a=multi(a,a,p);
b>>=1;
}
return ans.x;
}
LL quick_pow(LL a,LL b,LL p)
{
LL ans=1;a%=p;
while(b)
{
if(b&1) ans=ans*a%p;
a=a*a%p;
b>>=1;
}
return ans;
}
int check(LL n,LL p)
{
return quick_pow(n,(p-1)/2,p);
}
LL mod(LL a,LL n)
{
a%=n;
if(a<0) return a+n;
return a;
}
void solve(int b,int p)
{
LL a,t;
while(1)
{
a=rand()%p;
t=a*a-b;
w=mod(t,p);
if(check(w,p)==p-1) break;
}
pii temp(a,1);
int ans=quick_pow_D(temp,(p+1)/2,p);
int ans2=p-ans;
if(ans>ans2) swap(ans,ans2);
printf("%d %d\n",ans,ans2);
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int a,n;
scanf("%d%d",&a,&n);a%=n;
if(n!=2&&check(a,n)==n-1) printf("No root\n");
else if(n==2)
{
if(a==1) printf("1\n");
else printf("No root\n");
}
else
{
solve(a,n);
}
}
}
第二十一章 离散对数
BSGS算法
BSGS算法全称Baby-Step-Giant-Step,即大小步算法,可以快速求解离散对数问题,形如
Ax
B(mod C),其中C为素数,以下所有讨论均基于gcd(a,c)=1的前提(C为素数,故也即A为C的倍数,可以较容易地特判掉)。
首先引入一个引理
这由欧拉公式可以很容易得出,由此若
无解那么更大的区域也一定无解了(当然
( C)也不是Ax的最小循环节,这一点结合原根的一些性质是很容易发现的),接下来我们需要一些分块的思想,我们首先需要设m=[
],我们假设x=i*m-j(
),也即Ax=Ai+m/Aj则原式可以转化为Aj*B
Ai+m(mod C),再打表枚举j为不同值时Aj*B的值,存入hash表中,再枚举i第一个出现在hash表中的结果即为解。
#include<cstdio>
#include<cmath>
#include<map>
using namespace std;
long long k,m;
map<long long,long long>mp;
inline long long multi(long long x,long long y,long long mod) //快速乘
{
long long tmp=(x*y-(long long)(((long double)x*y+0.5)/mod)*mod);
if (tmp<0) return tmp+mod; else return tmp;
}
long long quickpower(long long a,int b)
{
long long t=1;
while (b>0)
{
if ((b&1)==1) t=multi(t,a,m);
if (b>1) a=multi(a,a,m);
b=b>>1;
}
return t;
}
int main()
{
mp.clear();
scanf("%lld%lld",&k,&m);
long long now=(9*k+1)%m;
mp[now]=0;
int mm=ceil(sqrt(m));
for (int i=1;i<=mm;i++) //预处理哈希表
{
now=multi(now,10,m);
mp[now]=i;
}
now=1;
long long q=quickpower(10,mm);
for (int i=1;i<=mm;i++)
{
now=multi(now,q,m);
if (mp[now])
{
printf("%lld",(((long long)(i)*(long long)(mm)-mp[now])%m+m)%m);
return 0;
}
}
return 0;
}
扩展BSGS算法
BSGS只能解决C为素数的情况,为此引入扩展BSGS
令d=gcd(A,C),则A=ad,B=bd,C=cd若B不是d的倍数且非1则无解,
由于Ax
Bx(mod Cx)
A
B(mod C),故可以不断提取Ax与C的公因式,递归处理至Ax与C互质,将原方程处理至DAz
B’(mod C’)这时再用普通的BSGS算法求解即可记cnt=x-z,最后得出的答案再将cnt加回即可,需要注意的是,这样得出的解仅有仅有大于cnt的解,因此为了不漏解还需要枚举1-cnt。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
map<int, int> h;
int gcd(int a, int b) {return b ? gcd(b, a % b) : a;}
inline ll ksm(ll a, ll b, ll mod)
{
ll ret = 1, h = a;
while(b)
{
if(b & 1)
ret *= h, ret %= mod;
h *= h, h %= mod, b >>= 1;
}
return ret;
}
inline int exbsgs(int a, int b, int p)
{
a %= p, b %= p;
if(b == 1) return 0; //特判2
int g, d = 0;
ll c = 1;
while((g = gcd(a, p)) > 1)
{
if(b % g) return -1;
b /= g, p /= g, c = c * (a / g) % p, ++d;
if(c == b) return d; //特判3(其实等价于特判2)
}
h.clear();
int t = int(sqrt(p * 1.0) + 1);
ll base = b; //计算右半部分的值
for(int i = 0; i < t; i++)
h[base] = i, base = base * a % p;
base = ksm(a, t, p); //用左半边的值验证
ll now = c;
for(int i = 1; i <= t + 1; i++)
{
now = now * base % p;
if(h.count(now))
return i * t - h[now] + d; //返回解
}
return -1; //无解
}
int main()
{
int a, b, p;
while(~scanf("%d%d%d", &a, &p, &b) && a && b && p)
{
if(p == 1) {puts("0"); continue;} //特判1
int ans = exbsgs(a, b, p);
if(ans == -1) puts("No Solution");
else printf("%d\n", ans);
}
}
第二十二章 佩尔方程
形如x2-D y2=1(D是一个固定的正整数且D不是完全平方数的方程称为佩尔方程。
佩尔方程定理
设D是一个整数,且不是完全平方数,则佩尔方程x2-D
y2=1总有正整数解,如果(
,
)是使得
最小的解,则每个解可以通过去幂得到:
,(k=1,2,3…)
若D是完全平方数,则佩尔方程仅能得出一组通解:x=1,y=0。
连分数与佩尔方程
连分数
一个简单的实数可以表示为
为了方便,我们可以将它简记为X=[
]
连分数有性质:所有的无限连分数均是无理数,所有的无理数都可以一种精确的方式表示成无限连分数,可以用这种方法逼近一个无理数的值。
非完全平方数的平方根的连分数表示
一个非完全平方数的平方根的连分数都是以周期呈现的,如:
=[4;1,2,4,2,1,8,…],在之后就会循环出现1,2,4,2,1,8,我们不妨将这种形式记为
=[4;<1,2,4,2,1,8>],循环节长度为6,且为纯循环,循环节的最后一个数必然是
的两倍。
求佩尔方程的最小特解
我们记一有理数
设其循环节长度为n,则其解就有
由此得出佩尔方程的解。
int a[20000];
bool pell(int &x,int &y,int d){
int m=(int)sqrt((double)d);
if(m*m==d)//d不能为完全平方数
return false;
//将d以连分数形式存储
int num=0;//连分数数位
double sq=sqrt(d);//d的高精度根,相当于r0
a[num++]=m;//存储整数部分
int b=m;//当前整数部分
int c=1;//连分数最终展开时的分母
double temp;//连分数展开时的每一项
do{
c=(d-b*b)/c;
temp=(sq+b)/c;
a[num++]=(int)(floor(temp));
b=a[num-1]*c-b;
}while(a[num-1]!=2*a[0]);//当有一位等于整数两倍时结束
//将连分数形式化为分子分母形式,即求p、q两个值
int p=1,q=0;
for(int i=num-2;i>=0;i--){
int temp=p;
p=q+p*a[i];
q=temp;
}
if((num-1)%2){//连分数长度为奇数时
x=2*p*p+1;
y=2*p*q;
}
else{//连分数长度为偶数时
x=p;
y=q;
}
return true;
}
int main(){
int d;
while(scanf("%d",&d)!=EOF){
int x,y;
if(pell(x,y,d))
cout<<x<<" "<<y<<endl;
else
cout<<"No Solution"<<endl;
}
return 0;
}
第二十三章 阶与原根
阶
定义:若a与m是正整数,gcd(a,m)=1,使得ax
1(mod m)成立的最小的x,称为a对模m的阶,记
。
定理:
1.若m>0,gcd(a,m)=1,正整数x是同余式ax
1(mod m)的一个解当且仅当
2.如果a与m是互质的整数且m>0,那么正整数ai
aj(
),当且仅当i
j(mod
)
原根
定义:设m是正整数,a是整数,若a模m的阶等于
(m),则称a为模m的一个原根。
定理:
1.若模m有原根,那么它一共有
个原根;
2.如果p为素数,那么p一定存在原根,并且模p的原根个数为
(p-1);
3.m有原根的充要条件:m=2,4,pa,2pa,其中p是奇素数;
4.如果 a 与 m是互质的整数且n>0,则如果a是模m的一个原根,那么整数
,
, … ,
构成模m的既约剩余系。
既约剩余类,即简化剩余类,是指在每个模m的值与m互质的剩余类中,各取一数组成的集合。
这个定理说明了关于原根的一个基本性质,即
两两互不相同。
5.假设一个数g对于模p来说是原根,那么gi mod p的结果两两不同,且有
,
,那么g可以称为模的一个原根,归根到底就是gp-1
1(mod p)当且仅当指数为p-1的时候成立,这里的p是素数。
求模素数p的原根
对p-1进行因子分解,
即p-1=
是p-1的标准分解式,若恒有
成立,则g就是p的原根。(对于合数求原根,只需要把p-1换成
§即可)。
#include <iostream>
#include <string.h>
#include <algorithm>
#include <stdio.h>
#include <math.h>
#include <bitset>
using namespace std;
typedef long long LL;
const int N = 1000010;
bitset<N> prime;
int p[N],pri[N];
int k,cnt;
void isprime()
{
prime.set();
for(int i=2; i<N; i++)
{
if(prime[i])
{
p[k++] = i;
for(int j=i+i; j<N; j+=i)
prime[j] = false;
}
}
}
void Divide(int n)
{
cnt = 0;
int t = (int)sqrt(1.0*n);
for(int i=0; p[i]<=t; i++)
{
if(n%p[i]==0)
{
pri[cnt++] = p[i];
while(n%p[i]==0) n /= p[i];
}
}
if(n > 1)
pri[cnt++] = n;
}
LL quick_mod(LL a,LL b,LL m)
{
LL ans = 1;
a %= m;
while(b)
{
if(b&1)
{
ans = ans * a % m;
b--;
}
b >>= 1;
a = a * a % m;
}
return ans;
}
int main()
{
int P;
isprime();
while(cin>>P)
{
Divide(P-1);
for(int g=2; g<P; g++)
{
bool flag = true;
for(int i=0; i<cnt; i++)
{
int t = (P - 1) / pri[i];
if(quick_mod(g,t,P) == 1)
{
flag = false;
break;
}
}
if(flag)
{
int root = g;
cout<<root<<endl;
}
}
}
return 0;
}
第二十四章 整除性质
只有
种取值,
是
取值相同的一段区间的右端点。此性质常用于数论分块。
=
=
=
=
第二十五章 数论函数之加性函数
数论函数定义
定义域为整数,陪域为复数的函数,其中非常重要的一部分就是积性函数。
加性函数
对于一个函数f,如果对于gcd(a,b)=1满足f(ab)=f(a)+f(b),就称其为加性函数。
常见加性函数
质因子数
p为质数,gcd(a,b)=1说明a,b没有相同的质因子,所以这是加性函数。
质因子之和
p为质数
完全加性函数
对于一个函数f,如果对于任意a,b满足f(ab)=f(a)+f(b),就称其为完全加性函数。
常见完全加性函数:
可相同质因子数
p为质素,t为最大的整数满足pt|n
两数相乘,质因子数量可以直接合并。
可相同质因子之和
p为质素,t为最大的整数满足pt|n
第二十六章 数论函数之积性函数
积性函数
现在已知一个函数为数论函数,且有f(1)=1,则当其满足对于两个互质的数p和q都满足f(p
q)=f ( p)
f(q),称之为积性函数。
无需p,q互质也满足f(p
q)=f ( p)
f(q),则称其为完全积性函数。
常见积性函数
μ(n)——莫比乌斯函数
=
其可以用于一些有关素数的计数问题的容斥,以及莫比乌斯反演。
φ(n)——欧拉函数
小于n且与n互质的数的数量。
d(n)——约数函数
n的约数的个数。
σ(n)——约数和函数
n的约数的和。
常见完全积性函数
e(n)——元函数
I(n)——恒等函数
id(n)——单位函数
第二十七章 迪利克雷卷积
在算数函数集上,可以定义一种二元运算,使得这种运算为乘法,取普通函数加法为加法,使得算术函数集为一个交换环,其中一种这样的运算就是迪利克雷卷积。
运算
迪利克雷卷积满足
交换律:f
g=g
f
结合率:(f
g)
h=f
(g
h)
分配律:f
(g+h)=f
g+f
h=(g+h)
f
元函数与任意函数的卷积为函数本身f=f
e
对于任意数论函数如有
都有唯一的逆函数f-1,使得f
f-1=e,f-1定义为
f-1(n)=
常用卷积举例
1.莫比乌斯函数卷积恒等函数为元函数
也就是说
互为逆元。
2.欧拉函数卷积恒等函数为单位函数
同理也有
根据这个式子可以推出另一个非常巧妙的公式
3.恒等函数卷积恒等函数为约数个数函数
同理也有
4.恒等函数卷积单位函数为约数和函数
同理也就有
5.
同理
以此类推。
第二十八章 莫比乌斯反演
对于数论函数f(n)和F(n)满足
F(n)=
则定义其莫比乌斯反演为
f(n)=
还有一种更常见的反演形式为
f(n)=
第二十九章 杜教筛
有时我们需要在低于线性的复杂度下解决一些积性函数求和的问题,这时候就需要用到杜教筛。
求:
(f为积性函数)
分析:
如果可以使用线性复杂度,则这个问题将会非常容易解决,但是109的数据范围显然不允许我们使用线性的复杂度。为了解决这个问题我们需要先构造另外两个函数h,g使其有h=f
g,其中h,g函数都需要容易求前缀和。
设S(n)=
,我们对h函数求前缀和
通过移项可得
g(1)S(n)=
已知g和h的前缀和之后就可以通过整数分块递归地求解这个问题,这个算法的复杂度大约是O(
)
第三十章 min_25筛
虽然min_25筛乃至杜教筛名字上都有一个筛字,但是实际上并不能称作一种筛,准确的说,其应该是一种可以亚线性地复杂度下求出积性函数前缀和的算法。其中min25求解积性函数前缀和的而算法。其中min_25筛求解积性函数前缀和时需要满足条件。
f(x)当x为质数时有一个多项式表示
f(xc)在x为质数时可以快速计算。
对于答案,先考虑素数贡献,我们定义sp(n)表示 即前n个素数的积性函数和
这里我们先假设f对于质数的计算是完全积性函数
Pi表示线筛求出的第i小的质数
令g(n,i)表示
[j的最小质因数>
或j是质数]f(j)
在这里f(j)表示假设j是质数,以质数方式带入函数计算的结果
由于合数会被筛掉因而不会影响答案
考虑怎么计算g(n,i)
类似线性筛的方式每次筛掉一批合数
如果
>n则有g(n,i)=g(n,i−1)
因为第i个质数能筛掉的最小合数是
因此筛质数只需要筛到
即可
如果
n有
g(n,i)=g(n,i−1)−f(
)∗( g(
,i−1)−
)
原理是假设
是一个质因数,它能产生的合数贡献是f(
)∗g(
,i−1)
但是由于
不一定是最小质因数,还要加回多减的小质数即
由于满足f是完全积性函数,上面部分还算挺清真的
我们需要求的只是g(x,INF)
注意我们发现我们需要求的g(x,INF)只需要满足存在d使得x=
即可
可以提前整数分块这样只需要计算 数量级的g(x,INF)即可
可以通过滚动数组递推的方式完成这一部分
我们令S(n,m)表示
[i的最小质因数≥
]f(i)
显然我们要求的是S(n,1)
递归求解
贡献分两步统计:
质数贡献:
g(n,INF)−sp(m−1)
即去掉较小的质数以外其他质数都会被计算到
合数贡献:
即枚举当前选择的最小质因数以及数量转移,同时计算只选择多于两个当前因数即不往后转移的合数情况
这样直接转移就好了
例子:筛
发现phi(
)=
-1即对于指数的计算不是一个完全积性函数,这时候需要拆开计算,分别筛质数求值然后相减即可
推S(n,m)的时候也不会有影响,筛
等其余函数同理。
END
/*
撒花,终于更完了,恭喜自己数论的学习入土成功,也恭喜自己完成了第一篇完整的博客。
后续可能会有少许的修改和补充,应该不会大改了。
文章是根据本人的学习路线写(拼)的,基本是学一点,记一点,所以会比较乱,知识点的分类和顺序处理的不好,抱歉。
主要参考b站上的CSU-ICPC集训课程,IOI国家队论文,csdn和博客园的大佬们的博客,以及本(弱)校学长扔给我们的ppt。
板子一部分是网上抄来的,很多没用过,还有一部分手打,正确性不敢保证QAQ。
还有很多坑没有填,还有很多知识没有学,任重而道远,希望可以在这个过程中收获进步。
虽然应该不会有人看我写的东西,但还是希望自己可以坚持写下去,记录自己的学习历程,嘤嘤嘤。
*/