求两数的最大公约数gcd / 最小公倍数lcm
- 最大公约数(gcd)
更相减损法 在时间上显然没有 辗转相除法 优越,这里就不提了
循环写法:
int gcd(int a, int b) {
int t = a % b;
while (t)
{
a = b;
b = t;
t = a % b;
}
return b;
}
递归写法:
int gcd(int a, int b) {
return b ? gcd(b, a % b) : a;
}
- 最小公倍数(lcm)
两数的乘积 == 这两个数的 最大公约数gcd * 最小公倍数lcm
————————————————————————————————————————
快速幂 / 快速乘
如何快速地计算 a b a^b ab ,在 b b b 特别大的时候?
b 特别大的话循环乘会超时,快速幂的思想就是用二进制压缩乘的过程
- 快速幂
typedef long long ll;
ll ksm(ll a, ll b, ll mod) {
//求a的b次方,b特别大的话循环乘会超时,用二进制压缩也就是快速幂
ll ans = 1, rdw = a;
while (b)
{
//将 b 转化为二进制数看待,设b = 11,b(2) = 1011 = 2^3 * 2^1 * 2^0
//二进制数为 1 的位置是我们要累计的
if (b & 1)ans = (ans * rdw) % mod;
rdw = (rdw * rdw) % mod;//rdw 一路记录 2 的次方,有 1 的时候交给 ans
b >>= 1;
//枚举二进制位
}
return ans;
}
但是 r d w = ( r d w ∗ r d w ) 取 模 m o d rdw=(rdw*rdw)取模mod rdw=(rdw∗rdw)取模mod 的过程中,指数爆炸还是有可能导致 long long 都存不下的
所以就引入了快速乘的概念, a ∗ b a*b a∗b == b 个 a 相 加 b个a 相加 b个a相加
- 快速乘
ll ksc(ll a, ll b, ll mod) {
//求 a*b ,两数都很大的话有可能爆 ll ,用二进制 延长 取模的过程,使得大数被 mod 小
ll ans = 0, rdw = a;
while (b)
{
//b 个 a 也是可以由 b 拆分成多个二进制状态 累加得来
//拆分会使数尽可能小,能被 mod 得更 “平滑”,不容易爆 ll
if (b & 1)ans = (ans + rdw) % mod;
rdw = (rdw + rdw) % mod;
b >>= 1;
}
return ans;
}
综合:
typedef long long ll;
ll ksc(ll a, ll b, ll mod) {
ll ans = 0, rdw = a;
while (b)
{
if (b & 1)ans = (ans + rdw) % mod;
rdw = (rdw + rdw) % mod;
b >>= 1;
}
return ans;
}
ll ksm(ll a, ll b, ll mod) {
ll ans = 1, rdw = a;
while (b)
{
if (b & 1)ans = ksc(ans, rdw, mod);
rdw = ksc(rdw, rdw, mod);
b >>= 1;
}
return ans;
}
————————————————————————————————————————
埃筛/欧筛
如何在 O ( n l o g l o g n ) O(nloglog_n) O(nloglogn) 甚至在 O ( n ) O(n) O(n) 的时间内求出 n 以内所有的质数?
原理、证明比较复杂的我也半斤八两,这里只给模板
洛谷:P1217 [USACO1.5]回文质数
图片摘于 小白的学习笔记 的博客
根号压缩因子法:
- 一个数如果不是素数,就可以等于自身两个因子的乘积,这个因子大于这个数的根号后就会重复,产生运算冗杂,循环遍历到根号即可节约时间
- 这种做法只能判断某个数是否为质数,如果要找出 n 以内所有的质数,需要 O ( n 2 l o g n ) O(n^2logn) O(n2logn) 的时间
埃氏筛法
-
遍历 n 的过程中如果找到一个素数,这个素数的倍数一定不是素数,可以标记一下,这样到最后所有未被标记过的数就是 n 以内的素数
-
还可以简单优化一下,遍历 n 只需要遍历到 n \sqrt{n} n 即可,二重循环倍数的时候同样会把 n \sqrt{n} n 后的非素数标记
-
时间复杂度为 O ( n l o g l o g n ) O(nloglog_n) O(nloglogn) (有常数)
代码:
bool altshai[N];//false代表素数
void ashai(int m) {
//altshai[1] = false
int sqrtm = sqrt(m);
for (int i = 2; i <= sqrtm; i++) {
if (altshai[i])continue;//非素数跳过
for (int j = i * i; j <= m; j += i)//标记所有素数的倍数
altshai[j] = true;
}
}
欧拉筛
-
在埃拉托筛的基础上优化
-
埃拉托筛在遍历的素数取倍数的过程中,会有重复数据,如6是2、3素数的倍数,会重复标记,形成冗杂
-
欧拉筛严谨地排除了这些冗余,使得时间复杂度妥妥地在 O ( N ) O(N) O(N)
-
初始定义两个数组,ora 储存素数标记,sushu 储存用以乘积的因子(也就是素数),可以理解成倍数数组,实际上由于 sushu 数组储存的只是素数,所以数组大小可以不用开那么大
代码:
bool ora[N];//false代表素数
int sushu[N];
void orashai(int m) {
//ora[1] = false
int cnt = 0;
for (int i = 2; i <= m; i++) {
if (!ora[i])//如果是素数,就添加至倍数数组
sushu[cnt++] = i;
for (int j = 0; j < cnt; j++) {
if (i * sushu[j] > m)break;//只求范围内,超出就退出
ora[i * sushu[j]] = true;//某数 * 素数 必不可能是素数
if (i % sushu[j] == 0)break;//重点
//一旦是因子的倍数就退出
//除去重复的判断点
}
}
}
————————————————————————————————————————
小定理 / 公式
- 判断能否 整除十以内 的数的技巧
- 数论初等定理
Acwing:买不到的数目
数学论证:
证明:
结论:组合表达出来的糖数可以是
a
∗
x
+
b
∗
y
a*x+b*y
a∗x+b∗y,可以证得大于
a
∗
b
−
a
−
b
a*b-a-b
a∗b−a−b 的任何值都可以用此表示出来
- 数论小知识
“如果一个数是两个不同质数的乘积”,那这个数就不可能是一个质数和一个非质数的乘积,因为质数不可再分
但如果没有这个前提,一个数确实有这两种乘积的可能性