数学算法模板总结
素数
素数可以说是组成一个数的基本单位,关于素数的算法有线性筛素数,唯一分解定理与素数快速判断,这里来介绍一下线性筛素数的算法。
线性筛素数
void prim()
{
bool su[MAXN];
int sn[MAXN],num,n,t;
su[0]=1;su[1]=1;
for(int i=2;i<=n;i++)
{
if(!su[i]) sn[++num]=i;
for(int j=1;sn[j]*i<=n&&j<=num;j++)
{
su[i*sn[j]]=1;
if(!(i%sn[j])) break;
}
}
}
这个算法可以说”几乎”是的O(n)的,虽然程序看上去并不是。
这个算法保证了每个合数只被筛了一次。
因为这种算法保证每一个合数由其最小的素数和一个最大的合数筛去的。
if(!(i%sn[j]))break;
我们可以这样理解。i=p(a的最小质因子)*a(一个合数)。
如果i%sn[j]==0,那么p*a*sn[k+1] 可以被后面的a*prime[k+1]再与p筛出来。
因为p < sn[j+1], 所以再这时应该停止。
素数快速判断 Miller-Rabin算法
Miller-Rabin算法随机生成底数a,进行多次调用函数进行测试,Miller-Rabin检测也存在伪素数的问题,但是与费马检测不同,MR检测的正确概率不依赖被检测数p,而仅依赖于检测次数。已经证明,如果一个数p为合数,那么Miller-Rabin检测的证据数量不少于比其小的正整数的3/4,换言之,k次检测后得到错误结果的概率为(1/4)^k。我们在实际应用中一般可以测试15~20次。
欧几里德算法&&扩展欧几里德算法
欧几里德算法也叫辗转相除法,可以证明,
gcd(a,b)=gcd(b,a%b)
而扩展欧几里德算法,就是在求gcd的回推的基础上求出方程
ax+by=gcd(a,b)
的一个特征解。于是,我们就可以用其来解/判断是否有解一个二元不定方程了。
int e_gcd(int a,int b,int& x,int& y)
{
if(!b) return x=1,y=0,a;
int ans=e_gcd(b,a%b,y,x);
return y-=a/b*x,ans;
}
我们也可以用其求逆元,在下面会提到。
逆元
因为mod不满足除法,所以我们需要用到逆元来求a/b的模数。逆元就相当与(1/b)mod p
逆元有两种求法。第一种就是用扩展欧几里德来求解。
我们有同余方程
如果有解,x就算a模p意义下的逆元。
可以变成
用扩展欧几里德来求解。
线性筛逆元
其实还有线性的方法筛出**质素**p的逆元。
我们设
设 p=x∗a+r,r<a,1<i<p
有我们知道
两边同时乘以 a−1∗r−1 得到
整理得
代码
a[1]=1;
for(int i=2;i<p;i++)
a[i]=-(p/i)*a[p%i];
BSGS
BSGS(baby step giant step)就是求同余方程
我们设 x=im−j ,将其带入式子可以得到
aim−j≡b(modp)
即 aim≡b∗aj(modp)
我们可以计算 b∗ajmodp (i=0 to m)的值(快速幂) 将其放入map中 map[b∗ajmodp]=j
之后再枚举a的im次mod p的值,如果发现再map有对应解
ans=m∗i−map[aimmodp]
可以证明,当 m=p‾‾√ 最快。
为什么 x=im−j ,即x的最大值不超过p.
有公式即 ak%p−1≡ak(modp) (当a与p互质),通过小费马证得。
k 可以看成 k−m(p−1) 于是上面的式子变成了
ak(a(p−1))m≡ak(modp)
由于小费马, ap−1≡1(modp) ,可以得到
ak1m≡ak(modp)
这样就确保了x比p小。
LL BSGS(LL y,LL z,LL p)
{
map<LL,LL> ma;
LL m=sqrt(p),tmp=0;
ma.clear();
if(y%p==0&&z==0) return 1;
if(y%p==0&&z!=0) return -1;
for(int i=0;i<=m;i++)
{
if(!i) {tmp=z%p;ma[tmp]=i;continue;}
tmp=(tmp*y)%p;
ma[tmp]=i;
}
tmp=1;LL t=power(y,m,p);
for(int i=1;i*i<=p;i++)
{
tmp=(tmp*t)%p;
if(ma[tmp])
{
LL ans=i*m-ma[tmp];
return ans;
}
}
return -1;
}
扩展BSGS
如果p不一定是素数,可以这样
我们设
c=gcd(ax,p)
可以得到
设 x=k+i 使得 gcd(ai,p)=c
这样就可以用BSGS求 ak 模 cd 下逆元。
欧拉函数 ϕ(x)
ϕ(x)
表示不超过正整数x的数中与x互质的数的个数。
引理1
1.如果p为素数
ϕ(p)=p−1
2.如果n为素数p的x次方
ϕ(n)=ϕ(pa)=(p−1)∗pa−1
2.如果n=i*p,p为素数,且i%p==0
ϕ(n)=p∗ϕ(i)
2.如果n=i*p,p为素数,且i%p!=0
ϕ(n)=(p−1)∗ϕ(i)
3.如果n为素数a与素数b的乘积
ϕ(n)=ϕ(a∗b)=ϕ(a)∗ϕ(b)
引理2
n拆分乘
pa11∗pa21∗pa31∗pa41∗.....∗pakk
的素数幂乘积的表达式
ϕ(n)=n∗(1−1p1)∗(1−1p2)∗(1−1p3)∗......∗(1−1pk)
线性筛欧拉
因为上面的引理1,我们可以在筛素数的同时筛出欧拉。
#include <iostream>
#include <cstring>
using namespace std;
const int MAXN = 1e6+5;
bool su[MAXN];
int sn[MAXN],num,n,t;
int phi[MAXN];
void Get_phi()
{
su[0]=1;su[1]=1;phi[1]=1;
for(int i=2;i<=MAXN;i++)
{
if(!su[i]) sn[++num]=i,phi[i]=i-1;
for(int j=1;sn[j]*i<=MAXN&&j<=num;j++)
{
su[i*sn[j]]=1;
if(!(i%sn[j]))
{
phi[i*sn[j]]=phi[i]*sn[j];
break;
}
phi[i*sn[j]]=phi[i]*(sn[j]-1);
}
}
}
int main()
{
Get_phi();
return 0;
}
欧拉定理
如果a与m互质,则 aϕ(m)≡1(modb)