试除法求质数
分解质因数
筛质数 (欧拉线性筛)
#include <iostream>
using namespace std;
const int N = 1000010;
int primes[N], cnt;
bool st[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[i * primes[j]] = true;
// 若 i % primes[j] 说明i * primes[j] 最小质因子是 primes[j] ,则往后的i * primes[k] 最小质因子仍是 primes[j] ,
// 应交给后面的循环
if (i % primes[j] == 0) break;
}
}
}
int main() {
int n;
cin >> n;
get_primes(n);
cout << cnt << endl;
return 0;
}
试除法求约数
约数个数
思路: 分解质因数, 为各个质因数指数加一的乘积
约数之和
思路: 分解质因数,先对每个质因数求等比数列和,再将各个和相乘(将数组换成哈希表来存)
#include <iostream>
#include <unordered_map>
using namespace std;
const int mod = 1e9 + 7;
int main() {
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]++;
}
long long res = 1;
for (auto p : primes) {
int a = p.first, b = p.second;
long long t = 1;
while (b--) {
t = (t * a + 1) % mod;
}
res = res * t % mod;
}
cout << res << endl;
return 0;
}
最大公约数
思路: return (b ? gcd(b , a % b) : a);
定义法求欧拉函数
#include <iostream>
using namespace std;
int phi(int 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);
return res;
}
int main() {
int n;
cin >> n;
while (n--) {
int x;
cin >> x;
cout << phi(x) << endl;
}
return 0;
}
线性筛求欧拉函数
若primes[j] 整除 i ,则质因数组成不变, euler[i * primes[j]] = euler[i] * primes[j]
否则,根据欧拉函数积性和 euler[质数p] = p - 1, euler[i * primes[j]] = euler[i] * (primes[j] - 1)
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1000010;
int primes[N], cnt;
int eulers[N];
bool st[N];
void get_eulers(int x) {
eulers[1] = 1;
for (int i = 2; i <= x; i++) {
if (!st[i]) {
eulers[i] = i - 1;
primes[cnt++] = i;
}
for (int j = 0; i <= x / primes[j]; j++) {
st[i * primes[j]] = true;
if (i % primes[j] == 0) {
// 同样,按照线性筛的框架,能整除时要记得break
eulers[i * primes[j]] = eulers[i] * primes[j];
break;
}
eulers[i * primes[j]] = eulers[i] * (primes[j] - 1);
}
}
}
int main() {
int n;
cin >> n;
get_eulers(n);
long long res = 0;
for (int i = 1; i <= n; i++) res += eulers[i];
cout << res << endl;
return 0;
}
模版
#include <iostream>
using namespace std;
typedef long long LL;
LL qmi(LL a, LL b, LL p) {
LL res = 1 % p;
while (b) {
if (b & 1) res = res * a % p;
a = a * a % p;
b >>= 1;
}
return res;
}
int main() {
int n;
cin >> n;
while (n--) {
LL a, b, p;
cin >> a >> b >> p;
cout << qmi(a, b, p) << endl;
}
return 0;
}
快速幂求逆元
模版
#include <iostream>
using namespace std;
// x 和 y 要修改 ,故传引用 (a*x + b*y = gcd(a,b))
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 -= a / b * x;
return d;
}
int main() {
int n;
cin >> n;
while (n--) {
int a, b;
cin >> a >> b;
int x, y;
int d = exgcd(a, b, x, y);
cout << x << " " << y << endl;
}
return 0;
}
线性同余方程
#include <iostream>
using namespace std;
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 -= a / b * x;
return d;
}
int main() {
int n;
cin >> n;
while (n--) {
int a, b, m;
cin >> a >> b >> m;
int x, y;
int d = exgcd(a, m, x, y);
if (b % d) cout << "impossible" << endl;
else cout << (long long)b / d * x % m << endl;
}
return 0;
}
模版
思路: 每次合并两个方程(均由 x 的两种表示方式而来)
#include <iostream>
using namespace std;
typedef long long LL;
LL exgcd(LL a, LL b, LL& x, LL& y) {
if (!b) {
x = 1, y = 0;
return a;
}
LL d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
int main() {
int n;
cin >> n;
LL x = 0;
LL a1, m1;
cin >> a1 >> m1;
// 合并只有 n - 1 次, 注意循环次数
for (int i = 0; i < n - 1; i++) {
LL a2, m2;
cin >> a2 >> m2;
LL k1, k2;
int d = exgcd(a1, a2, k1, k2);
// 无解
if ((m2 - m1) % d) {
x = -1;
break;
}
// 合并方程,k1 作为新方程的 k 同步扩大
k1 *= (m2 - m1) / d;
// 变成mod (a2 / d) 意义下的, 由丢番图方程通解形式可知
k1 = (k1 % (a2 / d) + a2 / d) % (a2 / d);
// 更新 x 和 a1
x = k1 * a1 + m1;
m1 = k1 * a1 + m1;
// 更新 a 为两个方程里 a 的最小公倍数 (二者之积除以最大公因数)
a1 = abs(a1 / d * a2);
}
// 只剩最后一个方程时, 取 m1 在 0 ~ a1 范围内的代表元即可
if (x != -1) cout << (m1 % a1 + a1) % a1 << endl;
else cout << x << endl;
return 0;
}
模版
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdio>
using namespace std;
const int N = 110;
const double eps = 1e-8;
int n;
double a[N][N];
int gauss() {
int r = 0, c = 0;
// 注意以列为递增
for (; c < n; c++) {
int t = r;
// 把当前列绝对值最大的一行找出来
for (int i = r; i < n; i++) {
if (fabs(a[i][c]) > fabs(a[t][c])) t = i;
}
// 若该列全0,直接跳过
if (fabs(a[t][c]) < eps) continue;
// 交换
for (int i = c; i <= n; i++) swap(a[r][i], a[t][i]);
// 行首元素化一(注意倒序,因为要用到a[r][c])
for (int i = n; i >= c; i--) a[r][i] /= a[r][c];
for (int i = r + 1; i < n; i++) {
//当前列对应元素非零时才消元(同样注意倒序,因为要用到a[i][c])
if (fabs(a[i][c]) > eps) {
for (int j = n; j >= c; j--) a[i][j] -= a[i][c] * a[r][j];
}
}
r++; // 满足条件时 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 == 2) cout << "No solution" << endl;
else if (t == 1) cout << "Infinite group solutions" << endl;
else {
for (int i = 0; i < n; i++) printf("%.2f\n", a[i][n]);
}
return 0;
}
异或线性方程组
动态规划
预处理
Lucas定理
#include <iostream>
using namespace std;
typedef long long LL;
LL qmi(int a, int k, int p) {
LL res = 1;
while (k) {
if (k & 1) res = (LL)res * a % p;
a = (LL)a * a % p;
k >>= 1;
}
return res;
}
LL C(LL a, LL b, LL p) {
LL res = 1;
for (int i = 1, j = a; i <= b; i++, j--) {
res = (LL)res * j % p;
res = (LL)res * qmi(i, p - 2, p) % p;
}
return res;
}
LL lucas(LL a, LL b, LL p) {
if (a < p && b < p) return C(a, b, p);
return (LL)lucas(a / p, b / p, p) * C(a % p, b % p, p) % p;
}
int main() {
int n;
cin >> n;
while (n--) {
LL a, b, p;
cin >> a >> b >> p;
cout << lucas(a, b, p) << endl;
}
return 0;
}
Nim游戏模版
台阶Nim游戏
集合Nim游戏
#include <iostream>
#include <algorithm>
#include <cstring>
#include <unordered_set>
using namespace std;
const int N = 110, M = 10010;
int n, k;
int f[M], s[N];
int sg(int x) {
// 记忆化搜索
if (f[x] != -1) return f[x];
// 用集合存储所有可取到的值
unordered_set<int> S;
for (int i = 0; i < k; i++) {
int sum = s[i];
if (x >= sum) {
S.insert(sg(x - sum));
}
}
// SG函数的值是取不到的最小自然数(可以想象为链条从这里断开)
for (int i = 0;; i++) {
if (!S.count(i))
return f[x] = i;
}
}
int main() {
cin >> k;
memset(f, -1, sizeof f);
for (int i = 0; i < k; i++) cin >> s[i];
cin >> n;
int res = 0;
while (n--) {
int x;
cin >> x;
res ^= sg(x);
}
if (res) cout << "Yes" << endl;
else cout << "No" << endl;
return 0;
}
拆分Nim游戏
#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_set>
using namespace std;
const int N = 110;
int n;
int f[N];
int sg(int x) {
if (f[x] != -1) return f[x];
unordered_set<int> S;
// 取出原先石子,放入的新石子,每堆规模不超过原先,不妨设i那堆石子更多
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() {
cin >> n;
memset(f, -1, sizeof f);
int res = 0;
while (n--) {
int x;
cin >> x;
res ^= sg(x);
}
if (res) cout << "Yes" << endl;
else cout << "No" << endl;
return 0;
}