数论算法学习笔记
littleandylu · · 算法·理论
初级算法(知识点)
组合数
- 组合数:形如
\binom{n}{m} (或C_n^m )的数表示在n 个东西里面选m 个(无序)的方案数。代数表示为\frac{n!}{m!(n-m)!} - 组合数的性质:
1) 递推公式
C_n^m=C_{n-1}^m+C_{n-1}^{m-1} 2) 二项式定理(x+y)^n=\sum_{i=0}^nx^iy^{n-i} 3) 上式最常见的用法是取x=y=1 得\sum_{i=0}^nC_n^i=2^n - 与其有关的定理:
Lucas 定理
对于
若
其中,如果不足
证明
我们先对
令
我们构造一个
由此可见,因为
归纳成立。
一些疑惑:
- 如果
\exist\ 1\le i\le k 满足a_i\lt b_i ,那么就有p\mid C_n^m 。 -
题目
- 【模板】卢卡斯定理/Lucas 定理
- 【模板】扩展卢卡斯定理/exLucas
中国剩余定理
对于
则方程
必然有解。
证明
依然归纳。容易发现,对于前
依然是解。
由于
中的数形成
归纳成立。
一些疑惑
- 如果
\{a\} 并不是两两互质,请跳转 EXCRT。
题目
- 【模板】中国剩余定理(CRT)/ 曹冲养猪
- 【模板】扩展中国剩余定理(EXCRT)
裴蜀定理(应用:exgcd)
十分重要的定理(算法)。
对于
证明
我们先记
首先,第二问是非常容易的,因为
对于第一问,设
与中国剩余定理类似,因为
exgcd(拓展欧几里得算法)就是用来求一组满足要求的
int exgcd(int x, int y, int &u, int &v) {
if (!y) return u = 1, v = 0, x;
ans = exgcd(y, x % y, u, v);
int t = u; u = v, v = t - x / y * v;
return ans;
}
一些疑惑
这次没有疑惑。o(*≧▽≦)ツ
题目
- 【模板】二元一次不定方程 (exgcd)
- 青蛙的约会
其它内容
如果想要了解某些定理的拓展版本,请前往模板题或 oi-wiki。
高级一点的算法
数论函数
定义:若
进一步地,若数论函数
常见的数论函数包括
其中
具体地,若记
这里定义两个数论函数
并且称
线性筛
也称欧拉筛,一种可以在
代码模板如下:
void sieve(int N) {
phi[1] = miu[1] = 1;
for (int i = 2; i <= N; i++) {
if (!is_p[i]) p[++c] = i, phi[i] = i - 1, miu[i] = -1;
for (int j = 1; j <= c && i * p[j] <= N; j++) {
is_p[i * p[j]] = 1;
if (i % p[j] == 0) { // 保证 p[j] 是 i 的最小质因子
phi[i * p[j]] = phi[i] * p[j];
miu[i * p[j]] = 0;
break;
}
phi[i * p[j]] = phi[i] * (p[j] - 1);
miu[i * p[j]] = -miu[i];
}
}
}
莫比乌斯反演
因为莫比乌斯函数
- 若数论函数
f*1=g ,则有g*\mu=f 。
感性理解一下:我们如果认为
转化成代数式就是:
但是,更常用的却是另一个性质:
但是,最常用的却是
介于
题目
- [CQOI2015] 选数
- YY的GCD
- 简单的数学题
- 最小公倍数之和
这些都是很好的练习题(当然,求答案时肯定要用到很多科技)。
整除分块
几乎所有筛法必要的技能。
例如,如果我们要求
为了求这个,我们必须证明,
证明
- 若
i\le\sqrt n ,则i 有\sqrt n 种取值,故\lfloor\frac ni\rfloor 也至多有\sqrt n 种取值。 - 若
i\ge\sqrt n ,则\frac ni\le\sqrt n ,也是至多有\sqrt n 种取值。
证明完毕。
那么,我们就可以利用这个整除分块了,假设我们可以求得
具体求
for (int l = 1, r; l <= n; l = r + 1)
r = n / (n / l),
ans += (r - l + 1) * (n / r);
时间复杂度为
杜教筛
一种比线性筛快那么亿点点,有局限性,但是十分好写好吃的筛法。
为了方便,我们记所有
假设我们要求积性函数
首先,为了满足筛法要求我们设
这里
假设我们已经求出了
而因为
如果
所以,我们只需要从小到大遍历所有整除点,然后对每个整除点都做一遍就可以了!
使用前提
- 必须存在积性函数
f,h 使得f*g=h 且f,h 的前缀和可以快速求出。 - 但,值得庆幸的是,虽然这个约束条件非常严苛,但是两个极其重要的数论函数
\varphi(x) 以及\mu(x) 都可以配:
时间复杂度
杜教筛的时间复杂度
复杂度瓶颈在于对每个整除点整除分块,依然使用我们证明整除分块时间复杂度为
复杂度为
显然后面的式子可以直接忽略,所以
依然不够优秀。怎么办?想到可以预处理前
(部分过程同上,已省略)
那么,为了平衡复杂度,解方程
得
当然,介于存储用的 map 以及整除分块大常数,杜教筛没有预想的那么快,不过还是足够了。
题目
- 【模板】杜教筛
- [YDOI R1] Lattice
- [NOI2016] 循环之美
有一些题目在莫比乌斯反演中出现过,但是它们也需要用到杜教筛。
例题选讲
- [CQOI2015] 选数
题目大意:在区间
首先,注意到取的数必然是
我们记
于是使用莫比乌斯反演,
在
杜教筛+整除分块处理即可。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 1e9 + 7;
const int N = 5e6 + 5;
int miu[N], p[N];
bool is_p[N];
int n, k, L, R, c, ans;
map <int, int> mp;
void sieve(int N) { // 线性筛
miu[1] = 1;
for (int i = 2; i <= N; i++) {
if (!is_p[i]) p[++c] = i, miu[i] = -1;
for (int j = 1; j <= c && i * p[j] <= N; j++) {
is_p[i * p[j]] = 1;
if (i % p[j] == 0) {
miu[i * p[j]] = 0;
break;
}
miu[i * p[j]] = -miu[i];
}
}
for (int i = 2; i <= N; i++)
miu[i] += miu[i - 1];
}
int qpow(int x, int y, int res = 1) {
while (y) {
if (y & 1) (res *= x) %= mod;
(x *= x) %= mod; y >>= 1;
}
return res;
}
int cal_miu(int x) { // 杜教筛求 miu[x] 前缀和
if (x <= 5e6) return miu[x];
if (mp.count(x)) return mp[x];
int res = 1;
for (int l = 2, r; l <= x; l = r + 1)
r = x / (x / l),
(res -= (r - l + 1) * cal_miu(x / r)) %= mod;
return mp[x] = res;
}
signed main() {
scanf("%lld%lld%lld%lld", &n, &k, &L, &R);
L = (L - 1) / k, R /= k; sieve(5e6);
for (int l = 1, r; l <= R; l = r + 1) { // 整除分块(注意除以 0!)
r = R / (R / l); if (L / l) r = min(r, L / (L / l));
(ans += (cal_miu(r) - cal_miu(l - 1)) * qpow(R / r - L / r, n)) %= mod;
}
return printf("%lld", (ans + mod) % mod), 0;
}
Powerful Number 筛(PN 筛)
有时我们可能遇到杜教筛无法解决的函数,这时我们就可能需要用到这个筛法。
首先,我们定义 Powerful Number 为形如
我们现在证明
证明完毕。
假设我们要求的前缀和函数为
首先,由于
再次利用
这样我们就能算
如果我们能够用科技求出
题目
- 【模板】Min_25 筛
例题选讲
- 【模板】Min_25 筛
虽然写的是 Min_25 筛,但是我们其实可以用 PN 筛做。
根据筛法条文,我们应该先构造
注意到
接下来我们就需要知道,怎样快速求出
最后一个难点就是,如何求出
考虑将 Powerful Number 肢解,让它没那么有 Power分解质因数,根据
并且,因为
类似杜教筛,我们再次将
但是这样依然不够简洁。我们考虑,能不能直接推出
先找找规律:
-
h(1)=1 -
h(p)=f(p)-g(p)=0 -
h(p^2)=f(p^2)-g(p)h(p)-g(p^2)=p^2(p^2-1)-p^3(p-1)=p^3-p^2 -
h(p^3)=p^3(p^3-1)-p^3(p-1)^2-p^5(p-1)=2(p^4-p^3) -
h(p^4)=p^4(p^4-1)-2p^4(p-1)^2-p^5(p-1)^2-p^7(p-1)=3(p^5-p^4)
数多了之后渐渐地发现了规律:由于太复杂了我不会证qwq
然后根据积性函数的性质求出
求 Powerful Number 的过程用 dfs 就行,枚举当前质数的指数即可。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 1e9 + 7;
const int inv2 = 500000004;
const int inv6 = 166666668;
const int N = 5e6 + 5;
int p[1000000], phi[N], c;
bool is_p[N];
void sieve(int N) { // 线性筛
phi[1] = 1;
for (int i = 2; i <= N; i++) {
if (!is_p[i]) p[++c] = i, phi[i] = i - 1;
for (int j = 1; j <= c && i * p[j] <= N; j++) {
is_p[i * p[j]] = 1;
if (i % p[j] == 0) {
phi[i * p[j]] = phi[i] * p[j];
break;
}
phi[i * p[j]] = phi[i] * (p[j] - 1);
}
}
for (int i = 2; i <= N; i++) (phi[i] *= i) %= mod;
for (int i = 2; i <= N; i++) (phi[i] += phi[i - 1]) %= mod;
}
int mp[N], n, ans;
int cal_G(int x) { // 杜教筛筛 g
if (x <= 5e6) return phi[x];
if (mp[n / x]) return mp[n / x];
int X = x % mod;
int res = X * (X + 1) % mod * (2 * X + 1) % mod * inv6 % mod;
for (int l = 2, r; l <= x; l = r + 1)
r = x / (x / l),
(res -= (l + r) % mod * (r - l + 1) % mod * inv2 % mod * cal_G(x / r)) %= mod;
return mp[n / x] = res;
}
void dfs(int nw, int num, int H) { // num 表示当前 h 值
if (nw > c || num * p[nw] > n) {
(ans += H * cal_G(n / num)) %= mod;
return;
}
dfs(nw + 1, num, H); // 当前质数不取
for (int mu = p[nw] * p[nw], s = 1; num * mu <= n; mu *= p[nw], s++)
dfs(nw + 1, num * mu, s * (p[nw] - 1) % mod * mu % mod * H % mod); // 计算 h 公式
}
signed main() {
scanf("%lld", &n), sieve(5e6), dfs(1, 1, 1);
return printf("%lld", ans), 0;
}
再见~