话说当初我不选择数学竞赛与其他竞赛的原因,就是我以为信息学竞赛有关数学的方面不怎么较难。结果到数论时,我就GG了,所以这次借着复习,巩固数论。
(注:本文以实用和总结为主,大部分定理与公式是没有证明的)
数论小集(基础篇)
1.质数
既然质数的英文名叫做“Prime number”,而Prime又有“基础的”的意思,那么它是很重要的。
判断一个数是不是质数可以用O(sqrt(n))的复杂度(枚举1~sqrt(n)的数
),但是若要求1~n的质数,则可以用欧拉筛以O(n)的算法求出。代码:
void Prime(int n)//
{
cnt=0;//cnt从零开始
memset(vis,0,sizeof(vis));
for(int i=2;i<n;i++)
{
if(!vis[i])
prime[cnt++]=i;
for(int j=0;j<cnt&&i*prime[j]<n;j++)//prime数组从零开始
{
vis[i*prime[j]]=1;
if(i%prime[j]==0)
break;
}
}
return ;
}
其实还可以用这个来求每个数的最小质因子,稍作修改即可(推荐:BZOJ3233)
void Prime(int num)
{
cnt=0;
memset(minn,0,sizeof(minn));
for(int i=2;i<=num;i++)
{
if(!minn[i])
{
prime[cnt++]=i;
minn[i]=i;
}
for(int j=0;j<cnt&&i*prime[j]<=num;j++)
{
minn[i*prime[j]]=prime[j];
if(i%prime[j]==0) break;
}
}
}
2.gcd与lcm
gcd是最大公因数,lcm是最小公倍数。
gcd用辗转相除法,复杂度为O(logn)的,而lcm则建立在gcd之上:lcm(a,b)=a/gcd(a,b)*b
代码:
int gcd(int a,int b)//最大公约数
{
return b==0?a:gcd(b,a%b);
}
int lcm(int a,int b)//最小公倍数
{
return a/gcd(a,b)*b;//注意:这里应该先除后乘,减少溢出的可能
}
3.模运算基础
有:
(a+b)%mod=((a%mod)+(b%mod
))%mod
(a*b)%mod=((a%mod)*(b%mod))%mod
(a-b)%mod=((a%mod)-(b%mod)+mod)%mod
以及快速幂(O(logn))取模:
int pow(int base,int b)//建议使用循环,而不用递归(容易崩栈!血的教训!)
{
int res=1;
while(b)
{
if(b&1) res=res*base%mod;
base=base*base%mod;
b>>=1;
}
return res;
}
4.(重点)扩展欧几里得算法
扩展欧几里得能够找出一对(x,y),使得ax+by=gdc(a,b),并有以下简易结论:
结论1: 若方程ax+by=c的一组整数解为(x0,y0),则它的任意整数解都可以写成(x0+kb',y0-ka'),其中a'=a/gcd(a,b),b'=b/gcd(a,b),k取任意整数。
结论2: g=gcd(a,b),方程ax+by=g的一组解是(x0,y0),则当c是g的倍数时ax+by=c的一组解是(x0*c/g,y0*c/g);否则无解。
结论2: g=gcd(a,b),方程ax+by=g的一组解是(x0,y0),则当c是g的倍数时ax+by=c的一组解是(x0*c/g,y0*c/g);否则无解。
它的作用很多,以后应该会讲到,其中最基本的就是他可以解两元一次方程组(利用以上两个结论)。
代码:
void gcd(int a,int b,int g,int &x,int &y)
{
if(!b) {g=a;x=1;y=0;}
else {gcd(b,a%b,g,y,x);y-=x*(a/b);}
}
5.计数问题
基础的加法原理,乘法原理,以及容斥原理应该都很简单(很多dp都会用)
主要是C(n,m),其实P(n,m)都用得很少。
关于C(n,m)简单的有以下几种算法
void C(int n,int m)
{
/*1.*/for(int i=1;i<=n;i++)
{
f[i][i]=f[i][0]=1;
for(int j=1;j<=i;j++)
{
f[i][j]=f[i-1][j]+f[i-1][j-1];//排列数的递推公式
}
}//递推式
/*2.*/f[n][m]=n!/m!(n-m)!//公式,可以预处理阶乘,但由于要用除法,n和m太大后要溢出,
//取模又没有除法运算(但有逆元),总之就是比较蛋疼的算法
/*3.*/c[0]=1;利用C(n,k)=(n+1-k)/k*C(n,k-1)
for(int i=1;i<=n;i++)c[i]=c[i-1]*(n-i+1)/i;实际意义不明显,但。。。很好玩
}
以上就是一部分笔者觉得重要的基础,虽然应用感觉很少,但是接下来大多数都建立在这些之上,至少把代码记住还是很重要的。