总结:
1.数论
2.组合计数
3.高斯消元
4.简单博弈论
数论:
1.质数:
定义:质数是指大于1的自然数中,如果只包含1和本身两个约数,这个数就叫质数,或者叫素数。
(1)质数的判定--试除法:
时间复杂度:固定O(sqrt(n))
代码:
bool is_prime(int n)
{
if (n < 2) return false;
for (int i = 2; i <= n / i; i ++ )
if (n % i == 0)
return false;
return true;
}
【注意】:
这里for循环里面 不推荐写 i <= sqrt(n),因为这样的话每次一个i都要计算一下sqrt(n)会很慢,同时,i * i <= n 也不推荐,因为当 n 比较接近于int的最大值时, i 的平方会有溢出风险就变成负数了, 所以推荐写 i <= n / i
(2)分解质因数--试除法:
时间复杂度:最坏O(sqrt(n)) 最好O(logn)
分解质因数(Prime Factorization)是指将一个正整数表示为其质数因子的乘积的过程。具体来说,就是将一个大于1的自然数写成若干个质数的乘积形式。n = p1^a1 * p2^a2 * p3^a3.....pn^an
n里面最多只包含一个大于 sqrt(n)的质因子,假设有两个,两个相乘就大于n了,所以成立
代码:
void divide(int x)
{
for (int i = 2; i <= x / i; i ++ ) // 优化,只遍历到根号x
{
// 即使遍历到了一个合数(如 4、6、8 等),
// 这些合数的因子已经在前面的步骤中被去除了,
if (x % i == 0)
{
int s = 0;
while (x % i == 0)
{
x /= i;
s ++;
}
cout << i << ' ' << s << endl;
}
}
if (x > 1) cout << x << ' ' << 1 << endl;
// 如果变量 x 的值仍然大于 1,那么此时的 x 必然是一个大于 1 的质数,
// 因为在此之前已经用所有可能的质数因子去除过了。
cout << endl;
}
(3)筛质数:
给定一个正整数 n,请你求出 1∼n 中质数的个数。
(i) 最普通的筛法 :
时间复杂度:O(nlogn)
思路:
i从2开始枚举到n,将i的倍数全部标记(筛掉)
代码:
int primes[N], cnt; // primes存储所有质数 cnt是质数的数量
bool st[N]; // true表示被筛掉了
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (!st[i]) primes[cnt ++] = i; // 如果没被筛掉 说明是质数 就加进去
for (int j = i + i; j <= n; j += i) // 把倍数全部筛掉
{
st[j] = true;
}
}
cout << cnt << endl;
}
(ii) 埃氏筛法 :
时间复杂度:O(nloglogn)
优化思路:
因为每一个数我们都可以分解质因数,所以我们只需要筛掉所有质数的倍数就行了
质数定理:1到n中有 n / ln n个质数
代码:
int primes[N], cnt; // primes存储所有质数 cnt是质数的数量
bool st[N]; // true表示被筛掉了
void get_primes(int n)
{
for (int i = 2; i <= n; i ++ )
{
if (!st[i])
{
primes[cnt ++] = i; // 如果没被筛掉的话,说明是质数
for (int j = i + i; j <= n; j += i)
// 把质数的倍数全部筛掉
{
st[j] = true;
}
}
}
cout << cnt << endl;
}
(iii) 线性筛法:
时间复杂度: O(n)
思路:
之前筛法中,每个合数可能会被筛多次,为了确保每个合数只会被筛选一次,我们用每个合数的最小质因子来筛选,因为每个合数的最小质因子只有一个,所以每个合数只会被筛选一次,就是n 只会被他的最小质因子筛掉,所以说他是线性的。
首先 primes[j] * i 的最小质因子是 min(primes[j], i 的最小质因子)
1.当 i % primes[j] != 0 时, 因为primes是从小到大存的所有质数,所以说明此时primes[j] 是小于i的最小质因子的,所以 primes[j] * i的最小质因子就是 primes[j]
2.当 i % primes[j] == 0时, 同样因为primes是从小到大存的所有质数,所以当这种情况发生时,primes[j] 就是 i 的最小质因子 ,所以primes[j] * i 的最小质因子就是 primes[j]
所以,用primes[j]来筛掉 primes[j] * i 是可以的
当 i % primes[j] == 0 时,应该终止,因为如果不终止,下一次循环时会筛掉 primes[j + 1] * i 但是 primes[j + 1] * i 的最小质因子是 min(primes[j + 1], i的最小质因子) 很显然 primes[j + 1] 是大于primes[j] 的,i的最小质因子不变,并且 因为 i % primes[j] == 0 说明i的最小质因子是 primes[j] ,所以 筛掉的primes[j +1] * i 的最小质因子就是 min (primes[j + 1], primes[j]) 显然是primes[j] ,这个数应该是由 primes[j] 筛掉的,为了避免筛选重复,所以在这里循环终止
为什么能筛掉所有合数:
因为每个合数都有一个最小质因子,对于一个合数x,假设pj是x的最小质因子,i在枚举到x之前,一定会枚举到 x / pj,在这个时候就会被筛掉
代码:
int primes[N], cnt; // primes存储所有质数 cnt是质数的数量
bool st[N]; // true表示被筛掉了
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 ++ ) // primes[j] <= n / i就是 primes[j] * i <= n
{
st[primes[j] * i] = true; // 因为primes[j]一定是他的最小质因子,所以把这个数筛掉
if (i % primes[j] == 0) break;
}
}
cout << cnt << endl;
}
2.约数
约数(Divisor)是指能够整除另一个数的数。具体来说,如果一个整数 a 能够被另一个整数 b 整除(即除以 b 的结果是一个整数,没有余数),那么 b 就是 a 的一个约数。
(1)试除法求所有约数:
时间复杂度:O(sqrt(n))
思路:
i从1开始遍历到sqrt(n),只遍历较小的因数,如果取模等于0,那就是这个数的约数
代码:
vector<int> get_divisors(int n)
{
vector<int> res;
for (int i = 1; i <= n / i; i ++ )
{
if (n % i == 0)
{
res.push_back(i);
if (i != n / i) res.push_back(n / i); // 特判一下i的平方等于n这种情况
}
}
sort(res.begin(), res.end());
for (auto x : res) cout << x << ' ';
cout << endl;
return res;
}
(2)求约数个数:
算术基本定理指出:
- 每个大于1的自然数都可以唯一地分解为一组质数的乘积。
- 这种质因数分解是唯一的,除了因子的排列顺序之外。
所以每个数都可以写成 N = p1 ^ a1 * p2 ^ a2 * ...pk ^ ak,同时N的每个约数也都可以写成
d = p1 ^ b1 * p2 * b2 *... pk * bk 这里 0 <= bk <=ak, 所以N的约数的个数就等于 b1 到 bk的不同取法,因为每个数的分解质因数是唯一的,所以第一个有0到b1个选法,同样到最后约数个数就是(a1+1)(a2+1)...(ak+1)
代码在下面
(3)求约数之和:
公式: 约数之和 = (p^0 + p^1 + p^2 ... p^a1) *...*(pk^0 + pk^1 + ... +pk^ak)
思路:
例题:
题目:
给定 n 个正整数 ai,请你输出这些数的乘积的约数之和,答案对 109+7 取模。
输入格式
第一行包含整数 n。
接下来 n 行,每行包含一个整数 ai。
输出格式
输出一个整数,表示所给正整数的乘积的约数之和,答案需对 109+7 取模。
数据范围
1 ≤ n ≤ 100,
1 ≤ ai ≤ 2×10^9
输入样例:
3
2
6
8
输出样例:
252
代码:
#include <iostream>
#include <algorithm>
#include <map>
#include <vector>
using namespace std;
typedef long long LL;
const int N = 110, mod = 1e9 + 7;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
unordered_map<int, int> primes;
while (n -- )
{
int x;
cin >> x;
for (int i = 2; i <= x / i; i ++ )
{
while (x % i == 0)
{
primes[i] ++;
x /= i;
}
}
if (x > 1) primes[x] ++;
}
LL res = 1; // 套用求约数和的公式算(p1^0+p1^1+…+p1^c1)∗…∗(pk^0+pk^1+…+pk^ck)
for (auto p : primes)
{
LL a = p.first, b = p.second;
LL t = 1;
while (b -- ) t = (t * a + 1) % mod; // b是指数,a是底数,那个质因数,取模是因为防止溢出
res = res * t % mod;
}
cout << res;
return 0;
}
如果是求个数很简单,套用求个数的公式就行
LL res = 1;
for (auto p : primes)
{
res = res * (p.second + 1) % mod;
}
(4)求最大公约数:
利用 欧几里得算法 (辗转相除法)时间复杂度是 O(logn) 。
算法思路和证明:
代码:
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a; // 当b等于0时,最大公约数为a,因为任何数与0的最大公约数为那个数
}