数论基础
数论
质数
在大于1的整数中,如果只包含1和本身这两个约数,就被称为约数,或者叫素数。
质数定理: 1~n中有 n/ln(n) 个质数
1. 质数的判定-试除法
bool is_prime(int x)
{
if (x < 2) return false;
for (int i = 2;i <= x / i;i++)
if (x % i == 0)
return false;
return true;
}
2. 分解质因数-试除法
n中最多只包含一个大于sqrt(n)的质因子
void divide(int x)
{
for(int i = 2; i <= x / i; i++)
if(x % i == 0) // i 一定是质数
{
int s = 0;
while(x % i == 0)
{
x /= i;
s++;
}
cout << i << s << endl;
}
if(x > 1) cout << x << 1 << endl;
}
3. 筛质数
朴素筛法
时间复杂度: n/2 + n/3 + ... + n/n = n(1/2 + 1/3 + ... + 1/n)
调和级数ln(n) < log2(n) 因此时间复杂度可表示为O(nlgn)
void get_primes(int n)
{
for(int i = 2; i <= n; i++)
{
if(!st[i])
{
primes[cnt++] = n;
}
for(int j = i + i; j <= n; j += i)
st[j] = 1;
}
}
埃氏筛法
优化: 只需筛掉质数的所有倍数 O(nloglogn)
void get_primes(int n)
{
for(int i = 2; i <= n; i++)
{
if(!st[i])
{
primes[cnt++] = n;
for(int j = i + i; j <= n; j += i)
st[j] = 1;
}
}
}
线性筛法
数据范围1e7时,比埃氏筛法快1倍
void get_primes(int n)
{
for(int i = 2; i <= n; i++)
{
if (!st[i]) primes[cnt++] = i;
for (int j = 0;primes[j] <= n / i;j++)
{
st[primes[j] * i] = 1;
if (i % primes[j] == 0) break; // primes[j]一定是i的最小质因子
}
}
}
约数
试除法求约数
set<int> get_divisors(int n)
{
set<int> res;
for (int i = 1; i <= n / i; i++) // 枚举到sqrt(n) 只筛小的约数
{
if (n % i == 0)
{
res.insert(i);
if (i != n / i) res.insert(n / i);
}
}
return res;
}
约数个数
分解质因数之后
公式:N=(α1+1)(α2+1)(α3+1)…(αk+1)
unordered_map<int, int> primes;
int n;
cin >> n;
while (n--)
{
int x;
cin >> x;
for (int i = 2;i <= x / i;i++)
{
while (x % i == 0)
{
x /= i;
primes[i]++;
}
}
if(x > 1) primes[x]++;
}
LL res = 1;
for (auto item : primes) res = res * (1 + item.second) % mod ;
cout << res << endl;
约数之和
公式:Sum=(1+p1+p1^2+…+p1^α1)(1+p2+p2^2+…+p2^α2)…(1+pk+pk^2+…+p1^αk)
// t = 1 while(a--) t = t * p + 1
for (auto item : primes)
{
int p = item.first, a = item.second;
LL t = 1;
while (a--)
{
t = (t * p + 1) % mod;
}
res = res * t % mod;
}
欧几里得算法(辗转相除法)
d|a d|b 那么 d|(ax+by)
(a, b) = (b, a mod b)
//__gcd(a,b) #include <algorithm>
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
欧拉函数
定义:1~n中与n互质的数的个数
分解质因数 N = p1^α1*p2^α2...pk^αk
φ(N) = N(1 - 1/p1)(1 - 1/p2)...(1 - 1/pk)
公式法
int x;
cin >> x;
int res = x;
for (int i = 2;i <= x / i;i++)
{
if (x % i == 0)
{
res = res / i * (i - 1);
while (x % i == 0)
x /= i;
}
}
if(x > 1) res = res / x * (x - 1);
cout << res << endl;
筛法求欧拉函数
线性筛法
i mod pj == 0 :
φ(pj * i) = pj * φ(i) 因为pj是i的最小质因子
i mod pj != 0 :
φ(pj * i) = pj * φ(i) * (1 - 1/pj) = φ(i) * (pj - 1)
pj 是pj*i的最小质因子且pj不在i的质因子当中的
//求1~n欧拉函数之和
LL get_eulers(int n)
{
phi[1] = 1;
for (int i = 2; i <= n;i++)
{
if (!st[i])
{
primes[cnt++] = i;
phi[i] = i - 1;
}
for (int j = 0;primes[j] <= n / i;j++)
{
st[primes[j] * i] = 1;
if (i % primes[j] == 0)
{
phi[primes[j] * i] = phi[i] * primes[j];
break;
}
//e[pj*i] = e[i]*pj*(1-1/pj) = e[i]*
phi[primes[j] * i] = phi[i] * (primes[j] - 1);
}
}
LL res = 0;
for (int i = 1;i <= n;i++) res += phi[i];
return res;
}
欧拉定理
a和n互质 则a^φ(n) 同余 1 (mod n)
a^φ(n) mod n = 1
费马定理 a^(p-1) % (p-1) = 1
快速幂
O(logk) 求 a^k mod p
LL qmi(int a, int k, int p)
{
LL res = 1;
while (k)
{
if (k & 1) res = res * (LL)a % p;
k >>= 1;
a = (LL)a * a % p;
}
return res;
}
快速幂求逆元
规定p为质数
1. a p互质
由费马定理 a^(p-1) = 1 (mod p) 由 a * a^(p-2) = 1 (mod p)
res = qmi(a, p - 2, p);
2. 不存在
扩展欧几里得算法
裴蜀定理:对于任意正整数a,b,一定存在非零整数x,y,使得ax+by = (a,b)
(a, b) = (b, a % b)
1. (a, 0) = a x = 1 y = 0
2. return (b, a % b) = d = by + (a mod b)x = by + (a - [a/b]*b)x
整理得到 ax + b(y - [a/b]*x) = d
故 x = x y = y - [a/b]*x
int exgcd(int a, int b, int &x, int &y)
{
if (!b)
{
x = 1, y = 0;
return a;
}
int d = exgcd(b, a % b, y, x);
y = y - a / b * x;
return d;
}
线性同余
$a_i × x_i ≡ b_i (mod\ m_i)$
化简为 $ax + my = b$
由扩展欧几里得 $ax + my = d = (a,b)$
因此若b为d的倍数,则$x = x × (b/d) % m$
若m不为质数 只能使用该方法求逆元 快速幂求逆元要求m为质数
中国剩余定理
x = a1 * M1 * M1-1 + ...
高斯消元
在$n^3$时间复杂度求出非齐次线性方程组 最简阶梯型矩阵
有3种情况
1. 无解 0 != 0
2. 无穷多解 出现 0 == 0
3. 唯一解 满秩
0表示唯一解 1表示无穷解 2表示无解
#include <iostream>
#include <cmath>
using namespace std;
int const N = 110;
const double eps = 1e-8;
int n;
double a[N][N];
int gauss()
{
int c, r; // column表示列 row表示行
for (c = 0, r = 0; c < n; c++)
{
int t = r;
for (int i = r; i < n; i++)
if (fabs(a[i][c]) > fabs(a[t][c])) // 找到次列绝对值最大的行
t = i;
if (fabs(a[t][c]) < eps) continue; // 全为0不用消啦
for (int i = c; i <= n; i++) swap(a[t][i], a[r][i]); // 将绝对值最大的行换到最顶端
for (int i = n; i >= c; i--) a[r][i] /= a[r][c]; // 将当前行的首位变成1
for (int i = r + 1; i < n; i++)
if (fabs(a[i][c]) > eps)
for (int j = n; j >= c; j--)
a[i][j] -= a[r][j] * a[i][c];
r++;
}
if (r < n)
{
for (int i = r; i < n; i++)
if (fabs(a[i][n]) > eps)
return 2; // 无解
return 1; // 无穷多解
}
for (int i = n - 1; i >= 0; i--)
for (int j = i + 1; j < n; j++)
a[i][n] -= a[i][j] * a[j][n];
return 0; // 有唯一解
}
int main()
{
cin >> n;
for (int i = 0; i < n; i++)
for (int j = 0; j <= n; j++)
cin >> a[i][j];
int t = gauss();
if (t == 0)
{
for (int i = 0; i < n; i++)
{
if(fabs(a[i][n]) < eps) a[i][n] = 0;
printf("%.2f\n", a[i][n]);
}
}
else if (t == 1)
puts("Infinite group solutions");
else
puts("No solution");
return 0;
}
组合计数
递推 1e3*1e3
//c[i][j] = c[i-1][j] + c[i - 1][j - 1]
void init()
{
for(int i = 0; i < N; i ++)
for(int j = 0; j <= i; j ++)
if(!j) c[i][j] = 1;
else c[i][j] = (c[i-1][j] + c[i - 1][j - 1]) % mod;
}
递推 1e5 * 1e5
void init()
{
fact[0] = infact[0] = 1;
for(int i = 1; i < N; i++)
{
fact[i] = (LL)fact[i - 1] * i % mod;
infact[i] = (LL)infact[i - 1] * qmi(i, mod - 2, mod) % mod;
}
}
//C(a,b) == (LL)fact[a] * infact[b] % mod * infact[a - b] % mod
// a! / b!(a - b)!
lucas
C(a,b) 同余 C(a%p,b%p) * C(a/p,b/p) (mod p)
#include <iostream>
using namespace std;
typedef long long LL;
int qmi(int a, int k, int p)
{
int res = 1;
while (k)
{
if(k & 1) res = (LL)res * a % p;
a = (LL)a * a % p;
k >>= 1;
}
return res;
}
int C(int a, int b, int p)
{
if(b > a) return 0;
int res = 1;
for(int i = a, j = 1; j <= b; i--, j ++)
{
res = (LL)res * i % p;
res = (LL) res * qmi(j, p - 2, p) % p;
}
return res;
}
int lucas(LL a, LL b, int p)
{
if(a < p && b < p) return C(a, b, p);
return (LL)C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;
}
int main()
{
int n;
cin >> n;
while (n--)
{
LL a, b;
int p;
cin >> a >> b >> p;
cout << lucas(a, b, p) << endl;
}
}
高精度
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int const N = 5010;
int primes[N], cnt, st[N];
int sum[N];
void get_primes(int n)
{
for (int i = 2; i <= n;i++)
{
if (!st[i]) primes[cnt++] = i;
for (int j = 0;primes[j] <= n / i;j++)
{
st[primes[j] * i] = 1;
if (i % primes[j] == 0) break;
}
}
}
int get(int n, int p)
{
int res = 0;
while (n)
{
res += n / p;
n /= p;
}
return res;
}
vector<int> mul(vector<int> a, int b)
{
int t = 0;
vector<int> c;
for (int i = 0;i < a.size();i++)
{
t += b * a[i];
c.push_back(t % 10);
t /= 10;
}
while (t)
{
c.push_back(t % 10);
t /= 10;
}
return c;
}
int main()
{
int a, b;
cin >> a >> b;
get_primes(a);
for (int i = 0;i < cnt;i++)
{
int p = primes[i];
sum[i] = get(a, p) - get(b, p) - get(a - b, p);
}
vector<int> res;
res.push_back(1);
for (int i = 0;i < cnt;i++)
for (int j = 0;j < sum[i];j++)
res = mul(res, primes[i]);
for (int i = res.size() - 1;i >= 0;i--) cout << res[i];
return 0;
}
卡特兰数
火车进站 括号序列 01串
C(2n,n) - C(2n,n-1) = C(2n,n)/(n+1)
int n;
cin >> n;
int a = 2 * n, b = n;
int res = 1;
for (int i = a, j = 1; j <= b; i--, j++)
{
res = (LL)res * i % mod;
res = (LL)res * qmi(j, mod - 2, mod) % mod;
}
res = (LL)res * qmi(n + 1, mod - 2, mod) % mod;
容斥原理
C(n,0) + C(n,1) + .. + C(n,n) = 2^n
可以看成 求n个物品的所有组合方案
这就是容斥原理的时间复杂度
//能被整除的数
#include <iostream>
using namespace std;
typedef long long LL;
int const N = 20;
int p[N];
int main()
{
int n, m;
int res = 0;
cin >> n >> m;
for (int i = 0; i < m; i++) cin >> p[i];
for (int i = 1; i < 1 << m; i++)
{
int cnt = 0, t = 1;
for (int j = 0; j < m; j++)
if (i >> j & 1)
{
if ((LL)t * p[j] > n)
{
t = -1;
break;
}
t *= p[j];
cnt++;
}
if (t != -1)
{
if (cnt % 2) res += n / t;
else res -= n / t;
}
}
cout << res << endl;
return 0;
}
简单博弈论
先手必胜态:可以走到某一个必败状态
先手必败态:走不到任何一个必败状态
Nim游戏
$a_1⊕a_2⊕ ... ⊕a_n = x$
if(x) 先手必胜
else 后手必胜
台阶Nim游戏
只需处理奇数台阶上的数
SG函数
在有向图游戏中,对于每个节点x,设从x出发共有k条有向边,分别到达节点y1,y2,····yk,定义SG(x)的后记节点y1,y2,····
yk的SG函数值构成的集合在执行mex运算的结果,即:
SG(x)=mex({SG(y1),SG(y2)····SG(yk)})
特别地,整个有向图游戏G的SG函数值被定义为有向图游戏起点s的SG函数值,即 SG(G)=SG(s).
集合Nim游戏
#include <iostream>
#include <cstring>
#include <unordered_set>
using namespace std;
int n, m, res;
int const N = 1000010;
int s[N], f[N];
int sg(int x)
{
if (f[x] != -1) return f[x];
unordered_set<int> S;
for (int i = 0; i < m; i++)
{
if (s[i] > x) continue;
S.insert(sg(x - s[i]));
}
for (int i = 0; ; i++)
if (!S.count(i))
return f[x] = i;
}
int main()
{
cin >> m;
for (int i = 0; i < m; i++) cin >> s[i];
cin >> n;
memset(f, -1, sizeof f);
for (int i = 0; i < n; i++)
{
int x;
cin >> x;
res ^= sg(x);
}
if (res) puts("Yes");
else puts("No");
}
拆分Nim
#include <cstring>
#include <iostream>
#include <unordered_set>
using namespace std;
int const N = 110;
int f[N];
int sg(int x)
{
if (f[x] != -1) return f[x];
unordered_set<int> S;
for (int i = 0; i < x; i++)
for (int j = 0; j <= i; j++)
S.insert(sg(i) ^ sg(j));
for (int i = 0; ;i++)
if (!S.count(i))
return f[x] = i;
}
int main()
{
int n;
cin >> n;
int res = 0;
memset(f, -1, sizeof f);
for (int i = 0; i < n; i++)
{
int x;
cin >> x;
res ^= sg(x);
}
if (res) puts("Yes");
else puts("No");
}
相邻两个自然数互质
//异或前缀和
s[i] = s[i-1] ^ a[i];
//l ~ r
s[r] ^ s[l - 1]
//已知正方形两点坐标 求另外两点
int x3 = x2 - (y2 - y1);
int y3 = y2 - (x1 - x2);
int x4 = x1 - (y2 - y1);
int y4 = y1 - (x1 - x2);
分拆数
k部分分拆数
#include <iostream>
using namespace std;
typedef long long LL;
LL f[2500][30]; // f[i][j]表示将i拆分成j个数相加的方案数
// 集合划分 1. 包含1 f[i - 1][j - 1]
// 2. 所有数都大于1 f[i - j][j]
int main()
{
int n = 2022, k = 10;
f[0][0] = 1;
for (int i = 1;i <= n;i++)
for (int j = 1;j <= k;j++)
if (i >= j)
f[i][j] = f[i - 1][j - 1] + f[i - j][j];
cout << f[2022][10];
}
k部分互异分拆数
#include <iostream>
using namespace std;
typedef long long LL;
LL f[2500][30];
// 集合划分 有1 f[i - k][k - 1]
// 无1 f[i - k][k]
int main()
{
int n = 2022, k = 10;
f[0][0] = 1;
for (int i = 1;i <= n;i++)
for (int j = 1;j <= k;j++)
if (i >= j)
f[i][j] = f[i - j][j - 1] + f[i - j][j];
cout << f[2022][10];
// 379187662194355221
}

浙公网安备 33010602011771号