前言
省选以前的复习吧。。。(尽管一定选不上233333)
总结一下常用的数学知识。
线性筛素数
简介
线性筛素数是比较常用的算法之一。比普通的暴力要节约大量时间开销。
算法原理
其实是这样的:
每个数字都可以被打上标记。初始状态下,所有数字都没有标记。
然后在扫描到一个数字后,如果这个数字有标记,则直接跳过这个数字。
如果没有标记,先将这个数字存储为质数,然后将这个数的所有整数倍的数字(不包含自己)都打上标记。这样做的结果是将所有质数的倍数全部筛除。
这样做,筛选
N
以内的质数,只需要
另外,还有优化方案。
由于要将一个非质数分解成两个数的乘积,必定有其中一个数是比
sqrt(N)
小。
而所有非质数都能够被分解为质数的乘积。
所以,我们可以只预处理 0—sqrt(n) 的素数,将它们存储到一个数组里面,然后每次询问 sqrt(n)—N 区间的素数时,只需要将这个数与 0—sqrt(n) 的素数进行整除验证,如果没有能整除的素数,则这个数就是一个素数。
这样做,可以节约预处理的时间开销和空间开销(一大部分呢)。预处理开销 O(sqrt(N)) ,访问 0—sqrt(n) 开销 O(1) ,访问 sqrt(n)—N 开销 O(Primes(0—sqrt(n))) 。
编程实现
CPP代码
const int range=10000000;
int prime[10000],primes=0,handle_range;
bool notpri[10000];
void makeprime()
{
handle_range=sqrt(range)+10;
notpri[0]=notpri[1]=true;
for(int i=2;i<=handle_range;i++)
{
if(notpri[i]) continue;
for(int j=i*2;j<=handle_range;j+=i)
{
notpri[j]=true;
}
prime[++primes]=i;
}
}
bool isprime(int x)
{
if(x<=handle_range) return !notpri[x];
for(int i=1;i<=primes;i++)
{
if(x%prime[i]==0) return false;
}
return true;
}
使用本代码前,需要引用的库函数:
- cmath
当然,最好自己写。(然而没有人会傻到抄这样难看的代码吧233333)
排列组合
简介
排列组合也是常考的东西。数论问题经常要解决,有时候暴力也需要的。。。
阶乘
理论讲解
阶乘就是。。。再简单不过了吧。。。用“!”来表示。。。
N!=1∗2∗3∗⋅⋅⋅∗N
这东西很常用。。。但是通常来讲,都会对一个数取模。。。
代码
const int MAXN=100005;
const long long mod=1e9+7;
long long jc[MAXN];
void memjc(int range)
{
jc[0]=1;
for(int i=1;i<=range;i++)
{
jc[i]=jc[i-1]*i%mod;
}
}
(这东西没编译就放上来了。。。反正没人会看这种代码的23333)
排列
排列(
A
)是指:从
那么,它与组合的区别是:
拿ABC和CBA两个集合来讲:
对于排列,这两种就是不同的方式。
对于组合,这两种就是相同的方式。
计算公式是:
Amn=n(n−1)(n−2)⋅⋅⋅⋅⋅⋅(n−m+1)=n!(n−m)! 组合
相对的,组合( C )就是:从
n 个东西里面,取出 m 个进行组合的方式数。计算公式是:
Cmn=n(n−1)(n−2)⋅⋅⋅⋅⋅⋅(n−m+1)=n!m!(n−m)! 排列组合的其他公式
C0n+C1n+C2n+⋅⋅⋅+Ckn+⋅⋅⋅+Cnn=2n
C0n−C1n+C2n−C3n+⋅⋅⋅+(−1)nCnn=0
C0n+C2n+C4n+⋅⋅⋅=C1n+C3n+C5n+⋅⋅⋅=2n−1
Cmn=Cm−1n−1+Cm−1n
配合取模的排列组合
如果在阶乘预处理的时候进行了取模运算。。。(当然这种情况超级常见),那么就必须要用到乘法逆元了。
以下内容转载自:欧几里德算法
先说什么是乘法逆元。
一般来讲,如果要运算加法、减法、乘法、乘方,都应该满足以下式子:
(a+b)%c=(a%c+b%c)%c
(a−b)%c=(a%c−b%c)%c
(a⋅b)%c=(a%c⋅b%c)%c
ab%p=(a%p)b%p
然而这里出现了一个问题:
如果是除法,并不满足 (a/b)%c=(a%c/b%c)%c ,不信你代个数试试:
(63%3=2≠6%33%3%3=RuntimeError
那怎么实现对除法的取余呢?
这里就引入乘法逆元这个东西。
他可以达到这样的效果:
ab%k=(a⋅c)%k
你可以将它简单地理解为类似于倒数的东西,只不过是再对倒数取余而已,即:
(b⋅c)%k=1
所以注意,逆元是针对一个数而言的,并不是针对一个表达式。
更加详细的欧几里德算法论证等请参见原博客。
那么,对于已经取模的阶乘,就必须要用乘法逆元了。
Amn=n(n−1)(n−2)⋅⋅⋅⋅⋅⋅(n−m+1)=n!∗inverse((n−m)!)
Cmn=n(n−1)(n−2)⋅⋅⋅⋅⋅⋅(n−m+1)=n!∗inverse(m!)∗inverse((n−m)!)
超级常用!请务必牢记!
二项式定理
(a+b)n=C0nanb0+C1nan−1b1+...+Crnan−rbr+...+Cnna0bn在这里, (a+b)n 展开式的第 r+1 项为:
Tr+1=Crnan−rbr其 i 项系数可表示为
Cin ,即 n 取i 的组合数目, 因此系数亦可表示为杨辉三角。唯一分解定理
唯一分解定理是指所有正整数都可以被分解为质数的乘积。
例如: 268=22∗671
所以分解方法很简单,只需要对于每个数:
将这个数与这个数以内的质数相除。
对于每个质数,除到不能再除为止。除的次数就是对应分解的结果。
欧拉函数
欧拉函数是用于计算:
小于 n 的正整数中与
n 互质的数的数目( φ(1)=1 )通式:
φ(x)=x(1−1p1)(1−1p2)(1−1p3)⋅⋅⋅⋅⋅⋅(1−1pn)
(其中 p1,p2……pn 为 x 的所有质因数,x 是不为 0 的整数)这样的话,使用上面的唯一分解定理配合求解,真是美哉。
费马小定理
费马小定理(Fermat Theory)是数论中的一个重要定理,其内容为:
假如
p 是质数,且 gcd(a,p)=1 ,那么 ap−1≡1 (mod p) 。即:
假如 a 是整数,
p 是质数,且 a,p 互质(即两者只有一个公约数 1 ),那么a 的 (p−1) 次方除以 p 的余数恒等于1 。