「外出学习」数论学习笔记
取模#
模运算#
#include <iostream>
using namespace std;
int a, b, c;
int main() {
cin >> a >> b >> c;
cout << (a + b) % c << endl;
cout << ((a - b) % c + c) % c << endl;
cout << ((long long)a * b) % c << endl;
cout << (1ll * a * b) % c << endl;
return 0;
}
读入两个数 ,现在求 是多少?
#include <iostream>
using namespace std;
int n, p;
int main() {
cin >> n >> p;
int ans = 1;
for (int i = 1; i <= n; ++ i) {
ans = 1ll * ans * i % p;
}
cout << ans << endl;
return 0;
}
gcd、lcm#
:最大公因数。
:最小公倍数。
有两个数 ,求 。
// gcd(a, 0) = a
int gcd(int a, int b) {
if (b == 0) {
return a;
}
return gcd(b, a % b);
}
证明以上代码是 复杂度。
每次都砍一半,所以这是一个 复杂度的算法。
有 个数,,求这 个数的 。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int n;
int a[N];
int gcd(int a, int b) {
if (b == 0) {
return a;
}
return gcd(b, a % b);
}
int main() {
cin >> n;
for (int i = 1; i <= n; ++ i) {
cin >> a[i];
}
int ans = a[1];
for (int i = 2; i <= n; ++ i) {
ans = gcd(ans, a[i]);
}
cout << ans << endl;
return 0;
}
复杂度:,因为 ans
只能越来越小。
lcm(a, b) = a / gcd(a, b) * b;
计算 。
#include <bits/stdc++.h>
using namespace std;
int x, y, p;
int qpow(int x, int y, int p) {
if (y == 0) return 1;
int z = qpow(x, y / 2, p);
z = 1ll * z * z % p;
if (y & 1) {
z = 1ll * z * x % p;
}
return z;
}
int main() {
cin >> x >> y >> p;
cout << qpow(x, y, p);
return 0;
}
给你三个数 ,计算 。,同时不支持
__int128
。
将乘法转化为加法。
int qtimes(int x, int y, int p) {
if (y == 0) return 0;
int z = qtimes(x, y / 2, p);
z = (z + z) % p;
if (y & 1) {
z = (1ll * z + x) % p;
}
return z;
}
矩阵#
行 列的二维数组就是一个 行 列的矩阵。
矩阵加法#
把对应位置上的数加起来就行了。
矩阵减法#
对应位置上的数相减。
数乘矩阵#
矩阵乘法#
矩阵 能与矩阵 做乘法,当且仅当 是 行 列, 是 行 列。
(只有当第一个矩阵的列数等于第二个矩阵的行数是才能进行矩阵乘法,结果矩阵的行是第一个矩阵的行,列是第二个矩阵的列数)
struct matrix {
int n, m;
int z[10][10];
matrix() { // 构造函数
n = m = 0;
memset(z, 0, sizeof(z));
}
};
matrix m1, m2, m3;
matrix operator * (const matrix &m1, const matrix &m2) { // 重载运算符
matrix m;
m.n = m1.n, m.m = m2.m;
for (int i = 1; i <= m.n; ++ i) {
for (int j = 1; j <= m.m; ++ j) {
for (int k = 1; k <= m1.m; ++ k) {
m.z[i][j] += m1.z[i][k] * m2.z[k][j];
}
}
}
return m;
}
m3 = m1 * m2;
memset#
memset(z, 0, sizeof z);
memset(z, -1, sizeof z);
memset(z, 0x3f, sizeof z); // 赋成 0x3f3f3f3f
给两个数 ,求斐波那契而数列第 项 是多少。
暴力
cin >> n >> p;
int a = 0, b = 1;
for (int i = 2; i <= n; ++ i) {
int c = a + b;
if (c >= p) c -= p;
a = b, b = c;
}
matrix qpow(matrix x, int y) {
if (y == 1) return x;
matrix z = qpow(x, y / 2);
z = z * z;
if (y & 1) z = z * x;
return z;
}
int main() {
cin >> n >> p;
matrix A, B;
A.n = 1, A.m = 2;
A.z[1][1] = 1, A.z[1][2] = 0;
B.n = 2, B.m = 2;
B.z[1][1] = 1, B.z[1][2] = 1;
B.z[2][1] = 1, B.z[2][2] = 0;
matrix C = A * qpow(B, n);
int ans = C.z[1][2];
cout << ans % p << '\n';
return 0;
}
矩阵乘法没有交换律,有结合律。
有 个点,走 步,走到 号点的方案数。
设 为走了 步,走到了 的方案数。
cin >> n;
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= n; ++ j) {
cin >> z[i][j];
}
}
f[0][1][1] = 1;
for (int a = 1; a <= k; ++ a) { // 走了a步
for (int d = 1;d <= 1; ++ d) { // 凑矩阵
for (int b = 1; b <= n; ++ b) { // 走到了b
for (int c = 1; c <= n; ++ c) { // 第a-1步在c
f[a][d][b] += f[a - 1][d][c] * z[c][b];
}
}
}
}
cout << f[k][1][n] << '\n';
return 0;
我们可以把 f[a], f[a-1], z
看成矩阵。
即
P4159#
拆点,一条长度为 的边拆成五条长度为 的边。
cin >> n;
for (int i = 1; i <= n; ++ i) {
z[i][n + (i - 1) * 9 + 1] = 1;
for (int j = 1; j < 9; ++ j) {
z[n + (i - 1) * 9 + j][n + (i - 1) * 9 + j + 1] = 1;
}
}
初等数论#
质数(素数)#
除 之外,只有 和它本身作为因子的数就是质数。
所有正整数可分为 、素数和合数。
判断素数#
bool is_prime(int x) { // O(x)
if (x < 2) return false;
for (int i = 2; i < x; ++ i) {
if (x % i == 0) return false;
}
return true;
}
bool is_prime(int x) { // O(sqrt(x))
if (x < 2) return false;
int y = sqrt(x) + 0.5;
for (int i = 2; i <= sqrt(x); ++ i) {
if (x % i == 0) return false;
}
return true;
}
bool is_prime(int x) { // O(sqrt(x))
if (x < 2) return false;
for (int i = 2; i * i <= x; ++ i) {
if (x % i == 0) return false;
}
return true;
}
分解质因数#
void factorize(int x) { // 对 x 分解质因数
for (int a = 2; a * a <= x; ++ a) {
if (x % a == 0) {
++ cnt;
prime[cnt] = a;
while (x % a == 0) {
++ num[cnt];
x /= a;
}
}
}
if (x != 1) {
++ cnt;
prime[cnt] = x;
num[cnt] = 1;
}
}
CF45G#
有 ,把这些数分成最少的组,使得每一组的和都是质数。
对于任何一个大于等于 的偶数,它一定可以分成 个质数和,任何一个大于等于 的奇数,它一定可以拆成 个质数之和。
算出
如果 是偶数,那么这时答案是 , 是奇数,答案小于等于 。
当 时,答案为 。
如果 是质数,那么答案是 。
否则,答案是 。
逆元#
费马小定理:当 是质数 ,
费马小定理使用条件:
-
是质数
-
若 不为质数,但 ,则使用欧拉定理。
欧拉定理:
: 中有多少个数和 互质。
当 是质数时,。
当 时,可以认为不存在逆元。
怎么算?
-
枚举 ,判断是否互质。
-
是一个质数,。
-
,
-
,
int get_phi(int n) {
int phi = n;
for (int i = 2; i * i <= n; ++ i) {
if (n % i == 0) {
phi = phi / i * (i - 1);
while (n % i == 0) {
n = n / i;
}
}
}
if (n != 1) {
phi = phi / n * (n - 1);
}
return phi;
}
cin >> n >> p;
// fac[i] = i!
fac[0] = 1;
for (int i = 1; i <= n; ++ i) {
fac[i] = 1ll * fac[i - 1] * i % p;
}
// ifac[i] = 1 / i! i!的逆元
ifac[n] = ksm(fac[n], p - 2, p);
for (int i = n - 1; i >= 0; i --) {
ifac[i] = 1ll * ifac[i + 1] * (i + 1) % p;
// 1/i! = 1/(i + 1)! * (i + 1)
}
// inv[i] = 1 / i i的逆元
// 1 / i = (i - 1)! / i!
for (int i = 1; i <= n; ++ i) {
inv[i] = 1ll * fac[i - 1] * ifac[i] % p;
}
已知 到 的逆元,求 的逆元。
cin >> n >> p;
inv[1] = 1;
for (int i = 2; i <= n; ++ i) {
int k = p / i;
int r = p % i;
// p = k * i + r
// 0 = k * i + r (mod p)
// -r = k * i(mod p)
// 1 / i = -k / r(mod p)
inv[i] = 1ll * (p - k) * inv[r] % p;
}
米勒拉宾素性测试#
给定 ,如何判断 是不是质数。
如果 是质数,
-
-
存在 ,
若 是质数,则这两条性质至少有一条成立,但是不是质数时这两个性质也可能成立。
如果两个性质都不成立,则一定不是质数。
// n - 1 = d * 2^r
// 找一个 1 <= a < n
// 1. a^d % n = 1
// 2. 存在一个 0 <= i < r,使得 a ^ (d * 2) % n = n - 1
// 当 n 是质数时,两个性质至少一个成立
bool miller_rabin(int n, int a) { // 检查n,a能否满足两个性质中的任意一个
// log(n)
int d = n - 1, r = 0;
while (d % 2 == 0) {
d = d / 2, ++ r;
}
int x = qpow(a, d, n);
if (x == 1) return true; // 性质一
for (int i = 0; i < r; ++ i) { // 性质二
if (x == n - 1) return true;
x = 1ll * x * x % n;
}
return false;
}
bool is_prime1(int n) {
if (n < 2) return false;
for (int i = 1; i <= 23; ++ i) {
int a = rand() % (n - 1) + 1;
if (!miller_rabin(n, a)) return false;
}
return true;
}
int prime_list[] = {2, 3, 5, 7, 13, 23, 37, 73};
bool is_prime2(int n) {
if (n < 2) return false;
for (int i = 0; i < 8; ++ i) {
if (n == prime_list[i]) return true;
if (n % prime_list[i] == 0) return false;
if (!miller_rabin(n, prime_list[i] % n)) return false;
}
return true;
}
不定方程#
解方程 。
int exgcd(int a, int b, int &x, int &y) {
// 求 g = gcd(a, b)
// 以及 xa + yb = g
if (b == 0) {
x = 1, y = 0;
return a;
}
int xp, yp;
int g = exgcd(b, a % b, xp, yp);
// xp * b + yp * (a % b) = g
// xp * b + yp * (a - b * (a /b)) = g
// xp * b + yp * a - yp * b * (a / b) = g
// yp * a + (xp - yp * (a / b)) * b = g
x = yp, y = xp - yp * (a / b);
return g;
}
, 是整数,求 能够表示出来的最小正整数。
答案:
裴属定理。
,只有当 时才有解。
同余方程#
中国剩余定理#
最小的 为 。
void solve(int p1, int a1, int p2, int a2, int &p, int &a) { // 暴力
// x % p1 = a1
// x % p2 = a2
// x % p = a
// 时间复杂度: O(min(p1, p2))
if (p1 < p2) swap(p1, p2), swap(a1, a2);
int x = a1;
int g = gcd(p1, p2);
int l = p1 / g * p2;
while (x % p2 != a2 && x <= l) {
x += p1;
}
if (x > l) p = -1, a = -1;
else p = l, a = x;
}
void solve(int p1, int a1, int p2, int a2, int &p, int &a) {
// 不要求 p1, p2 互质
// x % p1 = a1
// x % p2 = a2
// x % p = a
// x = k1 * p1 + a1 = k2 * p2 + a2
// k1 * p1 - k2 * p2 = a2 - a1
int g, k1, k2;
g = exgcd(p1, p2, k1, k2);
// k1 * p1 + k2 * p2 = g
k2 = -k2;
// k1 * p1 - k2 * p2 = g
if ((a2 - a1) % g != 0) {
p = -1, a = -1;
}
else {
int k = (a2 - a1) / g;
k1 = k1 * k, k2 = k2 * k;
// k1 * p1 - k2 * p2 = a2 - a1
int x = k1 * p1 + a1;
p = p1 / g * p2;
a = (x % p + p) % p;
}
}
筛法#
给定一个整数 ,希望在尽量短的时间内把 的质数找出来。
-
-
Miller-Rabin
-
for (int a = 2; a <= n; ++ a) {
for (int b = a + a; b <= n; b += a) {
not_prime[b] = true;
}
}
for (int a = 2; a <= n; ++ a) {
if (not_prime[a] == false) {
prime[++ cnt] = a;
}
}
优化
for (int a = 2; a <= n; ++ a) {
if (not_prime[a] == false) {
for (int b = a + a; b <= n; b += a) {
not_prime[b] = true;
}
}
}
线性筛#
让每个数被它最小的质因子筛掉。
for (int i = 2; i <= n; ++ i) {
if (not_prime[i] == false) {
prime[++ cnt] = i;
}
for (int j = 1; j <= cnt; ++ j) {
int x = prime[j] * i; // 筛掉第 j 个质数的 i 倍
if (x > n) break;
not_prime[x] = true;
if (i % prime[j] == 0) break;
// y = prime[j + 1] * i
// i = prime[j] * k
// y = prime[j + 1] * prime[j] * k
}
}
bool
int
P3383#
欧拉筛求积性函数的值#
积性函数:当 ,
完全积性函数:
phi[1] = 1;
for (int i = 2; i <= n; ++ i) {
if (not_prime[i] == false) {
prime[++ cnt] = i;
phi[i] = i - 1;
}
for (int j = 1; j <= cnt; ++ j) {
int x = prime[j] * i; // 筛掉第 j 个质数的 i 倍
if (x > n) break;
not_prime[x] = true;
if (i % prime[j] == 0) {
phi[x] = prime[j] * phi[i];
break;
}
}
}
BSGS 算法#
给定三个数 , 是质数,解方程 。
暴力
int solve(int a, int b, int p) {
// O(p)
int v = 1;
for (int x = 0; x <= p - 2; ++ x) {
if (v == b) return x;
v = 1ll * v * a % p;
}
return -1;
}
为什么一定会在 处循环呢?
因为
对于该方程,要枚举 个数,那我们将这 个数分组, 为每组的大小
若第 组数中出现了 ,那么在第 组中,一定出现了 。
#include <set>
using namespace std;
int solve(int a, int b, int p) {
int s = sqrt(p);
int v = 1;
set<int> se;
for (int i = 0; i < s; ++ i) {
se.insert(v);
v = 1ll * v * a % p;
}
// O(p / s)
for (int i = 0; i * s <= p; ++ i) { // 看答案是否在第 i 行里面
// 要看 b * a (-is) 是否在第 0 行出现
int c = 1ll * b * qpow(qpow(a, i * s, p), p - 2, p) % p;
if (se.count(c) != 0) {
int v = qpow(a, i * s, p); // 第 i 行的第一个数
for (int j = i * s; ; ++ j) { // O(s)
if (v == b) return j;
v = 1ll * v * a % p;
}
}
}
return -1;
}
若取 ,则为
作者:yifan0305
出处:https://www.cnblogs.com/yifan0305/p/17398926.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
转载时还请标明出处哟!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通