文章目录
未完 待补。
推荐博客:
[总结]数论和组合计数类数学相关(定理&证明&板子)
[总结]其他杂项数学相关(定理&证明&板子)
总结]多项式类数学相关(定理&证明&板子)
[探究] OI中各种初级数论算法相关
板子:
慢速乘
在模数较大的情况下,防止爆 l o n g l o n g long\ long long long。
ll qmul(ll a,ll b,ll p)
{
ll ans=0;
while(b)
{
if(b&1)
ans=(ans+a)%p;
a<<=1;
a%=p;
b>>=1;
}
return ans;
}
快速乘
目的同上,但是时间复杂度是
O
(
1
)
O(1)
O(1)的。(精度出锅自己负责~ )
ll fmul(ll a,ll b,ll p)
{
return (a*b-(ll)((long double)a*b/p)*p+p)%p;
}
快速幂
ll qpow(ll a,ll b)
{
ll t1=a,t2=1;
while(b)
{
if(b&1)
t2=t2*t1%p;
t1=t1*t1%p;
b>>=1;
}
return t2;
}
矩阵快速幂
原理和快速幂一样,只不过需要自己构造矩阵、实现矩阵乘法,给一个样例吧。
struct Matrix
{
ll t[3][3];
Matrix()
{
t[0][0]=t[0][1]=t[2][0]=t[2][2]=1;
t[1][0]=2;
t[0][2]=t[1][1]=t[1][2]=t[2][1]=0;
}
};
Matrix mul(Matrix a,Matrix b)
{
Matrix c;
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
c.t[i][j]=0;
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
for(int k=0;k<3;k++)
c.t[i][j]=(c.t[i][j]+a.t[i][k]*b.t[k][j])%m;
return c;
}
Matrix qpow(ll b)
{
Matrix t1,t2;
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
{
t1.t[i][j]=(i==j?1:0);
}
while(b>0)
{
if(b&1)
t1=mul(t1,t2);
t2=mul(t2,t2);
b>>=1;
}
return t1;
}
G C D GCD GCD
int gcd(int a, int b)
{
return b == 0 ? a : gcd(b, a%b);
}
E X G C D EXGCD EXGCD
ll exgcd(ll a,ll b,ll &x,ll &y)
{
if(b==0)
{
x=1,y=0;
return a;
}
ll c=exgcd(b,a%b,y,x);
y-=a/b*x;
return c;
}
另外一种写法:
int exgcd(int a, int b, int &x, int &y)
{
if (b == 0)
{
x = 1; y = 0;
return a;
}
int c = exgcd(b, a%b, x, y);
int t = x;
x = y;
y = t-a/b*y;
return c;
}
t i p : a ∗ x + b ∗ y = g c d ( a , b ) tip:a*x+b*y=gcd(a,b) tip:a∗x+b∗y=gcd(a,b),当 a 、 p a、p a、p互质时,想要求 a a a模 p p p的乘法逆元,只需要调用 e x g c d ( a , p , x , y ) exgcd(a,p,x,y) exgcd(a,p,x,y),求得的 x x x即为答案。( x x x可能是个负数,需要注意一下)
费马大定理
费马小定理
那么我们可以得到逆元的又一求法,因为
a
∗
x
=
1
(
m
o
d
p
)
a*x=1(mod\ \ p)
a∗x=1(mod p),由费马小定理可得
x
=
a
p
−
2
m
o
d
p
x=a^{p-2}\ mod\ p
x=ap−2 mod p。
乘法逆元
普通的乘法逆元的求法如上,这里只提一下逆元的线性算法。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=3e6+5;
int n,p;
int inv[maxn]; //inv[i] 表示 i 模 p 的乘法逆元
void work()//求出 1-n 所有数在模 p 意义下的乘法逆元 要保证p是一个素数且 n<p
{
inv[1]=1;
for(int i=2;i<=n;i++)
inv[i]=(ll)(p-p/i)*inv[p%i]%p;
}
int main()
{
scanf("%d %d",&n,&p);
work();
for(int i=1;i<=n;i++)
printf("%d\n",inv[i]);
return 0;
}
再说一下阶乘逆元的线性求法:
C R T CRT CRT
t
i
p
s
:
tips:
tips: 代码中求逆元的方式比较通用,如果满足情况的话可以用费马小定理代替。
ll qmul(ll a,ll b,ll p)
{
ll ans=0;
while(b)
{
if(b&1)
ans=(ans+a)%p;
a<<=1;
a%=p;
b>>=1;
}
return ans;
}
ll fmul(ll a,ll b,ll p)
{
return (a*b-(ll)((long double)a*b/p)*p+p)%p;
}
ll exgcd(ll a,ll b,ll &x,ll &y)
{
if(b==0)
{
x=1,y=0;
return a;
}
ll c=exgcd(b,a%b,x,y);
ll t=x;
x=y;
y=t-a/b*y;
return c;
}
ll inv(ll a,ll p)// 求 a%p 的乘法逆元
{
ll x,y;
exgcd(a,p,x,y);
return (x%p+p)%p;
}
ll CRT()
{
ll ans=0,M=1;
for(int i=1;i<=n;i++)
M*=m[i];
for(int i=1;i<=n;i++)
{
ll tmp=M/m[i];
ans=(ans+fmul(fmul(a[i],tmp,M),inv(tmp,m[i]),M))%M;
}
return (ans+M)%M;
}
E X C R T EXCRT EXCRT
ll EXCRT()
{
ll ans=a[1],M=m[1];
ll x,y,gcd;
for(int i=2;i<=n;i++)
{
gcd=exgcd(M,m[i],x,y); // M * x + m[i] * y = gcd(M,m[i])
ll t1=((a[i]-ans)%m[i]+m[i])%m[i];
ll t2=m[i]/gcd;
if(t1%gcd!=0)
return -1; //无解
x=fmul(x,t1/gcd,t2);
ans+=x*M;
M*=t2;
ans=(ans%M+M)%M;
}
return ans;
}
勾股数组
威尔逊定理
筛素数
E r a t o s t h e n e s Eratosthenes Eratosthenes筛法:
int MAX=sqrt(1000000+0.5);
for(int i=2;i<=MAX;i++)
if(!vis[i])
for(int j=i*i;j<=1000000;j+=i)
vis[j]=1;
vis[1]=1;
最大值自己修改即可。
欧拉筛法(线性筛法):
int prime[MAX+5];
bool vis[MAX+5];
void Prime()
{
int k=-1;
for(int i=2;i<=MAX;i++)
{
if(!vis[i])//是素数
prime[++k]=i;
for(int j=0;j<=k&&i*prime[j]<=MAX;j++)
{
vis[i*prime[j]]=1;
if(i%prime[j]==0)
break;
}
}
}
欧拉公式/定理
上式中的
φ
(
m
)
φ(m)
φ(m)就是下面将要介绍的欧拉函数。
欧拉函数
φ
(
x
)
φ(x)
φ(x)的含义是:
[
1
,
x
]
[1,x]
[1,x]内与
x
x
x互质的数的个数
(
φ
(
1
)
=
1
)
(φ(1)=1)
(φ(1)=1)。
所以我们可以直接求出
φ
(
n
)
φ(n)
φ(n)的值:
ll phi(ll n) //直接法求欧拉函数值
{
ll ret=n;
ll i;
for(i=2;i*i<=n;i++)
{
if(n%i==0)
{
ret-=ret/i;
while(n%i==0)
n/=i;
}
}
if(n>1)
ret-=ret/n;
return ret;
}
欧拉函数还有几个重要的性质:
那么在欧拉筛中加入一些语句,就可以同时在线性时间内得到
[
1
,
n
]
[1,n]
[1,n]的欧拉函数值:
int prime[10000005];
bool vis[10000005];
int phi[10000005];
int k=-1;
void euler(int n)
{
phi[1]=1;//1要特判
for (int i=2;i<=n;i++)
{
if(!vis[i])//i是质数
{
prime[++k]=i;
phi[i]=i-1;
}
for (int j=0;j<=k&&prime[j]*i<=n;j++)
{
vis[i*prime[j]]=1;
if (i%prime[j]==0)
{
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
else
phi[i*prime[j]]=phi[i]*phi[prime[j]];//利用了欧拉函数是个积性函数的性质
}
}
}
证明如下:
欧拉降幂
给定
a
、
s
、
p
a、s、p
a、s、p,计算
a
s
m
o
d
p
a^s\ \ mod\ \ p
as mod p的值,如果
s
s
s在
l
o
n
g
l
o
n
g
long\ long
long long范围内,那么很简单,快速幂就可以解决。但是如果
s
s
s非常大,只能用字符串的形式表示,该怎么求解呢?
方法一:基于快速幂的思想,我们依然可以对这个较大的幂进行拆分,举个例子
a
2333
=
(
a
1
)
3
∗
(
a
10
)
3
∗
(
a
100
)
3
∗
(
a
1000
)
2
a^{2333}=(a^1)^3*(a^{10})^3*(a^{100})^3*(a^{1000})^2
a2333=(a1)3∗(a10)3∗(a100)3∗(a1000)2,那么直接上代码:
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn=1e6+5;
ll a,p;
char s[maxn];
inline ll qpow(ll a,ll b)
{
ll ans=1;
while(b)
{
if(b&1)
ans=ans*a%p;
a=a*a%p;
b>>=1;
}
return ans;
}
inline ll cal() //计算 a^s mod p
{
int len=strlen(s);
ll ans=1;
for(int i=len-1;i>=0;i--)
{
ans=ans*qpow(a,s[i]-'0')%p;
a=qpow(a,10)%p;
}
return ans;
}
int main()
{
while(~scanf("%lld%s%lld",&a,s,&p))
printf("%lld\n",cal());
return 0;
}
方法二:你这个方法一不行啊,在某些题目下还是会超时啊。那么就需要一种更加优秀的做法——欧拉降幂。
先回顾一下欧拉定理:
下面给出欧拉降幂公式:
这个是广义欧拉降幂公式,就是说不管
A
、
C
A、C
A、C是否互质,都可以用这个公式来计算。当然你仍然需要判断
B
B
B和
φ
(
C
)
φ(C)
φ(C)的值,当
B
<
φ
(
C
)
B<φ(C)
B<φ(C)时可直接用快速幂计算,不能带入上述公式。
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int maxn=2e7+5;
ll a,p;
char s[maxn];
ll euler(ll n) //得到φ(n)
{
ll ret=n,i;
for(i=2;i*i<=n;i++)
{
if(n%i==0)
{
n/=i;
ret=ret-ret/i;
while(n%i==0)
n/=i;
}
}
if(n>1)
ret=ret-ret/n;
return ret;
}
ll qpow(ll a,ll b)//计算a^b%p
{
ll ans=1;
while(b>0)
{
if(b&1)
ans=ans*a%p;
a=a*a%p;
b>>=1;
}
return ans;
}
int main()
{
scanf("%lld%lld%s",&a,&p,s);//计算a^s % p
ll oula=euler(p);
ll sum=0;
int len=strlen(s);
bool flag=0;
for(int i=0;i<len;i++)
{
sum=sum*10+s[i]-'0';
if(sum>=oula)
sum%=oula,flag=1;
}
if(flag)
sum+=oula;
printf("%lld\n",qpow(a,sum));
return 0;
}
哥德巴赫猜想
计算模 p p p的 k k k次根
比较简单,实现就不给了。
M i l l e r − R a b i n Miller-Rabin Miller−Rabin素数检测
更加详细的内容请自行百度了解。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int prime[12]={2,3,5,7,11,13,17,19,23,29,31,37};
ll fmul(ll a,ll b,ll p)
{
return (a*b-(ll)((long double)a*b/p)*p+p)%p;
}
ll qpow(ll a,ll b,ll p)
{
ll ans=1;
while(b)
{
if(b&1)
ans=fmul(ans,a,p);
a=fmul(a,a,p);
b>>=1;
}
return ans;
}
bool Miller_Rabin(ll x)
{
int s=0;
ll t=x-1;
if(x==2)
return 1;
if(x<2||!(x&1))
return 0;
while(!(t&1))
{
s++;
t>>=1;
}
for(int i=0;i<12&&prime[i]<x;++i)
{
ll a=prime[i];
ll b=qpow(a,t,x);
for(int j=1;j<=s;++j)
{
ll k=fmul(b,b,x);
if(k==1&&b!=1&&b!=x-1)
return 0;
b=k;
}
if(b!=1)
return 0;
}
return 1;
}
int main()
{
ll x;
int t;
scanf("%d",&t);
while(t--)
{
scanf("%lld",&x);
printf("%s\n",Miller_Rabin(x)?"Yes":"No");
}
return 0;
}