ACM模板
算法基础
一维前缀和
区间和快速查询。
#include <bits/stdc++.h>
using i64 = long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int tt;
std::cin >> tt;
while (tt--) {
int n, q;
std::cin >> n >> q;
std::vector<i64> v(n + 1), pre(n + 1);
for (int i = 1; i <= n; i++) {
std::cin >> v[i];
pre[i] = pre[i - 1] + v[i];
}
while (q--) {
int l, r;
std::cin >> l >> r;
std::cout << pre[r] - pre[l - 1] << "\n";
}
}
}
二维前缀和
区域和快速查询。
#include <bits/stdc++.h>
using i64 = long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, m, q;
std::cin >> n >> m >> q;
std::vector<std::vector<i64>> pre(n + 1, std::vector<i64>(m + 1));
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
std::cin >> pre[i][j];
pre[i][j] += pre[i][j - 1] + pre[i - 1][j] - pre[i - 1][j - 1];
}
}
while (q--) {
int x1, y1, x2, y2;
std::cin >> x1 >> y1 >> x2 >> y2;
std::cout << pre[x2][y2] - pre[x2][y1 - 1] - pre[x1 - 1][y2] + pre[x1 - 1][y1 - 1] << "\n";
}
}
一维差分
区间离线修改。
#include <bits/stdc++.h>
using i64 = long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, p, q;
std::cin >> n >> p >> q;
std::vector<i64> a(n + 1), pre(n + 1), diff(n + 2);
for (int i = 1; i <= n; i++) {
std::cin >> a[i];
diff[i] = a[i] - a[i - 1];
}
while (p--) {
int l, r, x;
std::cin >> l >> r >> x;
diff[l] += x;
diff[r + 1] -= x;
}
for (int i = 1; i <= n; i++) {
diff[i] += diff[i - 1];
pre[i] = pre[i - 1] + diff[i];
}
while (q--) {
int l, r;
std::cin >> l >> r;
std::cout << pre[r] - pre[l - 1] << "\n";
}
}
二维差分
区域离线修改。
#include <bits/stdc++.h>
using i64 = long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, m, q;
std::cin >> n >> m >> q;
std::vector<std::vector<i64>> v(n + 2, std::vector<i64>(m + 2)), diff(n + 2, std::vector<i64>(m + 2));
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
std::cin >> v[i][j];
diff[i][j] += v[i][j];
diff[i + 1][j] -= v[i][j];
diff[i][j + 1] -= v[i][j];
diff[i + 1][j + 1] += v[i][j];
}
}
while (q--) {
int x1, y1, x2, y2, c;
std::cin >> x1 >> y1 >> x2 >> y2 >> c;
diff[x1][y1] += c;
diff[x2 + 1][y1] -= c;
diff[x1][y2 + 1] -= c;
diff[x2 + 1][y2 + 1] += c;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
v[i][j] = v[i - 1][j] + v[i][j - 1] - v[i - 1][j - 1] + diff[i][j];
std::cout << v[i][j] << " \n"[j == m];
}
}
}
动态规划
背包DP
01背包
例题1
给定背包容量m和n件物品,每件物品有重量w和价值v,每件物品只能选一次。求在不超过背包容量m的条件下,选择若干物品总价值的最大值。
#include <bits/stdc++.h>
using i64 = long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, m;
std::cin >> n >> m;
std::vector<int> w(n + 1), v(n + 1), dp(m + 1);
for (int i = 1; i <= n; i++) {
std::cin >> w[i] >> v[i];
}
for (int i = 1; i <= n; i++) {
for (int j = m; j >= w[i]; j--) {
dp[j] = std::max(dp[j], dp[j - w[i]] + v[i]);
}
}
std::cout << dp[m] << "\n";
}
完全背包
例题1
给定背包容量m和n件物品,每件物品有重量w和价值v,每件物品可以选任意次。求在不超过背包容量m的条件下,选择若干物品总价值的最大值。
#include <bits/stdc++.h>
using i64 = long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, m;
std::cin >> n >> m;
std::vector<i64> w(n + 1), v(n + 1), dp(m + 1);
for (int i = 1; i <= n; i++) {
std::cin >> w[i] >> v[i];
}
for (int i = 1; i <= n; i++) {
for (int j = w[i]; j <= m; j++) {
dp[j] = std::max(dp[j], dp[j - w[i]] + v[i]);
}
}
std::cout << dp[m] << "\n";
}
多重背包
例题1
给定背包容量m和n组物品,每组物品有数量s、重量w和价值v。每组物品中的每件物品可以重复选择,但每组物品的总数不能超过其数量s。求在不超过背包容量m的条件下,选择若干物品总价值的最大值。
// 二进制优化
#include <bits/stdc++.h>
using i64 = long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, m;
std::cin >> n >> m;
std::vector<i64> dp(m + 1);
for (int i = 1; i <= n; i++) {
i64 v, w, s, x = 1;
std::cin >> w >> v >> s;
std::vector<i64> vec;
while (s >= x) {
vec.push_back(x);
s -= x;
x <<= 1;
}
if (s) {
vec.push_back(s);
}
for (auto k : vec) {
for (int j = m; j >= k * w; j--) {
dp[j] = std::max(dp[j], dp[j - k * w] + k * v);
}
}
}
std::cout << dp[m] << "\n";
}
分组背包
例题1
给定背包容量m和n件物品,每件物品有重量a、价值b和类型c,每组物品只能选一个。求在不超过背包容量m的条件下,选择若干物品总价值的最大值。
#include <bits/stdc++.h>
using i64 = long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int m, n;
std::cin >> m >> n;
std::vector<int> dp(m + 1);
std::vector<std::vector<int>> v(n + 1, std::vector<int>{0}), w(n + 1, std::vector<int>{0});
for (int i = 1; i <= n; i++) {
int a, b, c;
std::cin >> a >> b >> c;
w[c].push_back(a);
v[c].push_back(b);
}
for (int i = 1; i <= n; i++) {
for (int j = m; j >= 0; j--) {
for (int k = 1; k < v[i].size(); k++) {
if (j >= w[i][k]) {
dp[j] = std::max(dp[j], dp[j - w[i][k]] + v[i][k]);
}
}
}
}
std::cout << dp[m] << "\n";
}
线性DP
最长公共子序列LCS
例题1
给定长度分别为n和m的两个字符串s1和s2,计算它们的LCS。
#include <bits/stdc++.h>
using i64 = long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, m;
std::cin >> n >> m;
std::vector<std::vector<int>> dp(n + 1, std::vector<int>(m + 1));
std::string s1, s2;
std::cin >> s1 >> s2;
s1 = " " + s1;
s2 = " " + s2;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
dp[i][j] = std::max(dp[i - 1][j], dp[i][j - 1]);
if (s1[i] == s2[j]) {
dp[i][j] = std::max(dp[i][j], dp[i - 1][j - 1] + 1);
}
}
}
std::cout << dp[n][m] << "\n";
}
例题2
排列的LCS。
#include <bits/stdc++.h>
using i64 = long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
std::cin >> n;
std::vector<int> p1(n), p2(n), idx(n + 1), lcs;
for (int i = 0; i < n; i++) {
std::cin >> p1[i];
idx[p1[i]] = i;
}
for (int i = 0; i < n; i++) {
std::cin >> p2[i];
p2[i] = idx[p2[i]];
}
for (int i = 0; i < n; i++) {
auto it = lower_bound(lcs.begin(), lcs.end(), p2[i]);
if (it == lcs.end()) {
lcs.push_back(p2[i]);
} else {
*it = p2[i];
}
}
std::cout << lcs.size() << "\n";
}
最长下降子序列LDS
例题1
给定长度为n的数组a,它的LDS。
// 朴素版本
#include <bits/stdc++.h>
using i64 = long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; i++) {
std::cin >> a[i];
}
std::vector<int> dp(n, 1);
for (int i = 1; i < n; i++) {
for (int j = 0; j < i; j++) {
if (a[i] < a[j]) { // 与LIS只有符号的区别,添加=取决于是否严格递增(递减)
dp[i] = std::max(dp[i], dp[j] + 1);
}
}
}
int ans = *std::max_element(dp.begin(), dp.end());
std::cout << ans <<"\n";
}
// 二分优化版本
#include <bits/stdc++.h>
using i64 = long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
std::cin >> n;
std::vector<int> a(n), lds;
for (int i = 0; i < n; i++) {
std::cin >> a[i];
}
for (int i = 0; i < n; i++) {
auto it = std::lower_bound(lds.begin(), lds.end(), -a[i]); // 严格递减
// auto it = std::upper_bound(lds.begin(), lds.end(), -a[i]); // 不增
if (it == lds.end()) {
lds.push_back(-a[i]);
} else {
*it = -a[i];
}
}
std::cout << lds.size() << "\n";
}
最长上升子序列LIS
例题1
给定长度为n的数组a,它的LIS。
#include <bits/stdc++.h>
using i64 = long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
std::cin >> n;
std::vector<int> a(n), lis;
for (int i = 0; i < n; i++) {
std::cin >> a[i];
}
for (int i = 0; i < n; i++) {
auto it = std::lower_bound(lis.begin(), lis.end(), a[i]); // 严格递增
// auto it = std::upper_bound(lis.begin(), lis.end(), a[i]); // 不降
if (it == lis.end()) {
lis.push_back(a[i]);
} else {
*it = a[i];
}
}
std::cout << lis.size() << "\n";
}
区间DP
例题1
N堆石子环形摆放,每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数记为该次合并的得分,求将N堆石子合并成1堆的最小得分和最大得分。
#include <bits/stdc++.h>
using i64 = long long;
const i64 INF = 4e18;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
std::cin >> n;
std::vector<int> a(n * 2 + 1), pre(n * 2 + 1);
std::vector<std::vector<i64>> dpmin(n * 2 + 1, std::vector<i64>(n * 2 + 1)), dpmax(n * 2 + 1, std::vector<i64>(n * 2 + 1));
for (int i = 1; i <= n; i++) {
std::cin >> a[i];
a[i + n] = a[i];
}
for (int i = 1; i <= 2 * n; i++) {
pre[i] = pre[i - 1] + a[i];
}
for (int len = 2; len <= n; len++) { // 枚举长度
for (int i = 1, j = i + len - 1; j <= 2 * n - 1; i++, j++) { // 枚举起点终点
dpmin[i][j] = INF;
dpmax[i][j] = 0;
for (int k = i; k < j; k++) { // 枚举分割点,构造状态转移方程
dpmin[i][j] = std::min(dpmin[i][j], dpmin[i][k] + dpmin[k + 1][j] + pre[j] - pre[i - 1]);
dpmax[i][j] = std::max(dpmax[i][j], dpmax[i][k] + dpmax[k + 1][j] + pre[j] - pre[i - 1]);
}
}
}
i64 minn = INF, maxn = 0;
for (int i = 1; i <= n; i++) {
minn = std::min(minn, dpmin[i][i + n - 1]);
maxn = std::max(maxn, dpmax[i][i + n - 1]);
}
std::cout << minn << "\n" << maxn << "\n";
}
数位DP
例题1
求从1到N的数字中,包含数字“49”的数字的个数。
#include <bits/stdc++.h>
using i64 = long long;
i64 digit[20], dp[20][10]; // 数组大小20是因为10^20 > 2^63-1,dp数组的第二位10则用于和pos的前一个位置比较,一个位置上的数字%10之后不会大于9,因此开10即可。
i64 dfs(int pos, int pre, bool limit) {
if (pos == 0) { // 如果已经处理完所有位,则返回1,表示找到一个满足条件的数字。
return 1;
}
if (!limit && dp[pos][pre] != 0) { // 如果不受限且已经计算过该状态,则直接返回结果,避免重复计算。
return dp[pos][pre];
}
i64 maxn = limit ? digit[pos] : 9, res = 0; // 如果受限,则最大值为当前位的数值;否则为9。
for (int i = 0; i <= maxn; i++) {
if (pre == 4 && i == 9) { // 如果前一位是4且当前位是9,则跳过这种情况,因为不满足条件。
continue;
}
// 递归处理下一位,注意条件limit && i == digit[pos],limit是当前位数的限制,i == digit[pos]是对下一位的限制,例如520,当前位(百位)是5的时候才需要考虑下一位是不是应该不超过2。
res += dfs(pos - 1, i, limit && i == digit[pos]);
}
if (!limit) { // 不受限制则使用记忆化
dp[pos][pre] = res;
}
return res;
}
i64 calc(i64 x) {
int pos = 0;
while (x > 0) {
digit[++pos] = x % 10;
x /= 10;
}
return dfs(pos, 0, true);
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int tt;
std::cin >> tt;
while (tt--) {
i64 x;
std::cin >> x;
std::cout << x - calc(x) + 1 << "\n";
}
}
状压DP
例题1
给定一个长度为n的正整数序列a(\(1 \leq a \leq 18\))。现在可以选择两个不重叠的子串拼接起来,要求拼接后的序列中每个数字不能出现超过一次,求出拼接序列的最大长度。
#include <bits/stdc++.h>
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, mask = 1 << 18, ans = 0; // mask是总状态数
std::cin >> n;
std::vector<int> a(n + 1), dp((1 << 18) + 1);
for (int i = 1; i <= n; i++) {
std::cin >> a[i];
}
for (int i = 1; i <= n; i++) {
// i是起始位置,j是终止位置
for (int j = i, s = 0; j <= n; j++) { // s记录子串是不是出现了某个数字
if (s >> (a[j] - 1) & 1) { // 判断子串是否包含了a[j]
break;
}
s |= 1 << (a[j] - 1); // 记录这个数字
dp[s] = std::max(dp[s], j - i + 1); // 更新出现这几个数字时的最大值
ans = std::max(ans, dp[s]);
}
}
for (int i = 0; i < mask; i++) { // 枚举所有的状态
// 一种位运算的技巧,效果类似不断右移直到最低位是1再进行计算,此处是在不断枚举i的非空子集
for (int j = i; j > 0; j = i & (j - 1)) {
// 异或的性质可以保证i^j和j在二进制下不会有重合的数字,遍历更新最大值即可
ans = std::max(ans, dp[i ^ j] + dp[j]);
}
}
std::cout << ans << "\n";
}
概率DP
例题1
抽卡游戏,每次抽卡有a%的概率出货。如果连续c发没有出货,之后每多抽一发,出货的概率会增加b%。求出货的抽数期望。
#include <bits/stdc++.h>
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int tt;
std::cin >> tt;
while (tt--) {
int a, b, c;
std::cin >> a >> b >> c;
// 最坏情况下需要抽的次数
int maxn = (100 - a + b - 1) / b + c;
std::vector<double> dp(maxn + 1, 1.0);
for (int i = maxn - 1; i >= 1; i--) {
double p;
if (i <= c) {
p = a / 100.0;
} else {
p = std::min((a + (i - c) * b) / 100.0, 1.0);
}
// 当前次数的答案就是1加上失败的概率乘多抽一次的期望
dp[i] += (1.0 - p) * dp[i + 1];
}
std::cout << std::fixed << std::setprecision(6) << dp[1] << "\n";
}
}
数学
数论
快速幂
给定三个整数 \(a\)、\(b\) 和 \(p\),请你计算 \(a^b \mod p\) 的值。
#include <bits/stdc++.h>
using i64 = long long;
i64 ksm(i64 a, i64 n, i64 mod) {
i64 res = 1;
a = (a % mod + mod) % mod;
while (n) {
if (n & 1) {
res = (a * res) % mod;
}
a = (a * a) % mod;
n >>= 1;
}
return res;
}
int main() {
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
int a, b, p;
std::cin >> a >> b >> p;
std::cout << ksm(a, b, p) << "\n";
}
素数筛
给出范围内所有素数。
// 欧拉筛
#include <bits/stdc++.h>
const int N = 1e7 + 10;
std::vector<int> primes;
std::bitset<N> f;
void euler(int n) {
f[0] = f[1] = 1;
for (int i = 2; i <= n; i++) {
if (!f[i]) {
primes.push_back(i);
}
for (auto prime : primes) {
if (i * prime > n) {
break;
}
f[i * prime] = true;
if (i % prime == 0) {
break;
}
}
}
}
int main() {
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
euler(N);
}
// 埃氏筛,bitset或vec+bool优化下1e8性能仍然优于欧拉筛
#include <bits/stdc++.h>
const int N = 1e7 + 10;
std::vector<int> primes, minpf(N + 1);
std::bitset<N> f;
void Eratosthenes(int n) {
for (int i = 2; i <= n; i++) {
f[i] = true;
minpf[i] = i;
}
for (int i = 2; i * i <= n; i++) {
if (f[i]) {
for (int j = i * i; j <= n; j += i) {
f[j] = false;
minpf[j] = i;
}
}
}
for (int i = 2; i <= n; i++) {
if (f[i]) {
primes.push_back(i);
}
}
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
Eratosthenes(N);
}
素性探测质因式分解
例题1
大数素性探测与质因式分解。
#include <bits/stdc++.h>
using i64 = long long;
using i128 = __int128;
std::mt19937 rng(std::chrono::steady_clock::now().time_since_epoch().count());
namespace Factorizer {
std::vector<int> primes, least;
void sieve(int n) {
std::vector<int> nums;
least.assign(n + 1, 0);
for (int i = 2; i <= n; i++) {
if (least[i] == 0) {
least[i] = i;
nums.push_back(i);
}
for (auto p : nums) {
if (i * p > n) {
break;
}
least[i * p] = p;
if (p == least[i]) {
break;
}
}
}
primes = nums;
}
bool miller_rabin(i64 n) {
if (n <= 1 || (n != 2 && n % 2 == 0)) {
return false;
}
for (auto a : {3, 5, 7, 11, 13, 17, 19, 23, 29}) {
if (n % a == 0) {
return n == a;
}
}
if (n < 31 * 31) {
return true;
}
i64 d = n - 1;
while (d % 2 == 0) {
d /= 2;
}
for (i64 a : {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37}) {
if (n == a) {
return true;
}
i64 t = d, y = 1 % n;
for (i64 _t = t; _t != 0; _t >>= 1) {
if (_t & 1) {
y = (i128)y * a % n;
}
a = (i128)a * a % n;
}
while (t != n - 1 && y != 1 && y != n - 1) {
y = (i128)y * y % n;
t <<= 1;
}
if (y != n - 1 && t % 2 == 0) {
return false;
}
}
return true;
}
i64 pollard_rho(i64 n) {
if (n == 1 || miller_rabin(n)) {
return n;
}
i64 now = 0;
do {
i64 t = std::gcd(++now, n), r = t, g = 1;
if (t != 1 && t != n) {
return t;
}
do {
t = ((i128)t * t % n + now) % n;
r = ((i128)r * r % n + now) % n;
r = ((i128)r * r % n + now) % n;
} while ((g = std::gcd(abs(t - r), n)) == 1);
if (g != n) {
return g;
}
} while (now < n / now);
return 0;
}
std::vector<i64> factor(i64 n) {
if (n == 1) {
return {};
}
std::vector<i64> g, d;
d.push_back(n);
while (!d.empty()) {
auto v = d.back();
d.pop_back();
auto rho = pollard_rho(v);
if (rho == v) {
g.push_back(rho);
} else {
d.push_back(rho);
d.push_back(v / rho);
}
}
std::sort(g.begin(), g.end());
return g;
}
} // namespace Factorizer
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int tt;
std::cin >> tt;
while (tt--) {
i64 n;
std::cin >> n;
if (Factorizer::miller_rabin(n)) {
std::cout << "Prime\n";
} else {
std::cout << Factorizer::factor(n).back() << "\n";
}
}
}
欧拉函数
欧拉函数表示的是小于等于 \(n\) 和 \(n\) 互质的数的个数。
// 定义求
#include <bits/stdc++.h>
using i64 = long long;
i64 euler_phi(i64 x) {
i64 res = x;
for (int i = 2; i * i <= x; 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() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
std::cin >> n;
for (int i = 0; i < n; i++) {
int x;
std::cin >> x;
std::cout << euler_phi(x) << "\n";
}
}
// 筛法求
#include <bits/stdc++.h>
using i64 = long long;
const int N = 1e6 + 10;
i64 phi[N], vis[N], prime[N];
void init(i64 n) {
vis[1] = 1;
for (i64 i = 2; i <= n; i++) {
if (!vis[i]) {
vis[i] = 1;
prime[++prime[0]] = i;
phi[i] = i - 1;
}
for (i64 j = 1; j <= prime[0] && i * prime[j] <= n; j++) {
vis[i * prime[j]] = 1;
if (i % prime[j] == 0) {
phi[i * prime[j]] = phi[i] * prime[j];
break;
} else {
phi[i * prime[j]] = phi[i] * (prime[j] - 1);
}
}
}
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
i64 n;
std::cin >> n;
init(n);
i64 res = 0;
for (i64 i = 1; i <= n; i++) {
res += phi[i];
}
std::cout << res + 1 << "\n";
}
乘法逆元
如果一个线性同余方程 \(ax \equiv 1 \ (\text{mod} \ b)\),则 \(x\) 称为 \(a \ \text{mod} \ b\) 的逆元。
例题1
给定 \(n\),\(p\) 求 \(1∼n\) 中所有整数在模 \(p\) 意义下的乘法逆元。
// 快速幂求逆元
#include <bits/stdc++.h>
using i64 = long long;
i64 ksm(i64 a, i64 n, i64 mod) {
i64 res = 1;
a = (a % mod + mod) % mod;
while (n) {
if (n & 1) {
res = (a * res) % mod;
}
a = (a * a) % mod;
n >>= 1;
}
return res;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int a, mod;
std::cin >> a >> mod;
std::cout << ksm(a, mod - 2, mod) << "\n";
}
// exgcd求逆元
#include <bits/stdc++.h>
using i64 = long long;
void exGCD(i64 a, i64 b, i64 &x, i64 &y) {
if (b == 0) {
x = 1, y = 0;
return;
}
exGCD(b, a % b, y, x);
y -= a / b * x;
}
i64 inv(i64 num, i64 mod) {
i64 x, y;
exGCD(num, mod, x, y);
return (x % mod + mod) % mod;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int a, mod;
std::cin >> a >> mod;
std::cout << inv(a, mod);
}
// 线性求逆元
#include <bits/stdc++.h>
using i64 = long long;
i64 ksm(i64 a, i64 n, i64 mod) {
i64 res = 1;
a = (a % mod + mod) % mod;
while (n) {
if (n & 1) {
res = (a * res) % mod;
}
a = (a * a) % mod;
n >>= 1;
}
return res;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, mod;
std::cin >> n >> mod;
std::vector<i64> a(n + 1), pre(n + 1, 1), suff(n + 1), inv(n + 1);
for (int i = 1; i <= n; i++) {
std::cin >> a[i];
pre[i] = pre[i - 1] * a[i] % mod;
}
suff[n] = ksm(pre[n], mod - 2, mod);
for (int i = n; i >= 1; i--) {
suff[i - 1] = suff[i] * a[i] % mod;
}
for (int i = 1; i <= n; i++) {
inv[i] = pre[i - 1] * suff[i] % mod;
std::cout << inv[i] << " \n"[i == n];
}
}
线性同余方程
例题1
求关于 \(x\) 的同余方程 \(ax \equiv 1 \ (\text{mod} \ b)\) 的最小正整数解。
#include <bits/stdc++.h>
using i64 = long long;
i64 exGCD(i64 a, i64 b, i64 &x, i64 &y) {
if (b == 0) {
x = 1;
y = 0;
return a;
}
i64 d = exGCD(b, a % b, y, x);
y -= a / b * x;
return d;
}
i64 calc(i64 a, i64 b, i64 m) {
i64 x, y;
i64 g = exGCD(a, m, x, y);
if (b % g != 0) {
return -1;
}
x = x * (b / g) % m;
if (x < 0) {
x += m;
}
return x;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
std::cin >> n;
while (n--) {
i64 a, b, m;
std::cin >> a >> b >> m;
i64 res = calc(a, b, m);
if (res == -1) {
std::cout << "impossible\n";
} else {
std::cout << res << '\n';
}
}
}
中国剩余定理
例题1
给定 \(n\) 组非负整数 \(a_i, b_i\),求解关于 \(x\) 的方程组的最小非负整数解(扩展的区别在于模数是不是互质)。
// 扩展中国剩余定理
#include <bits/stdc++.h>
using i64 = long long;
i64 gsc(i64 a, i64 b, i64 m) {
i64 res = 0;
while (b) {
if (b & 1) {
res = (res + a) % m;
}
a = (a + a) % m;
b >>= 1;
}
return res;
}
i64 exGCD(i64 a, i64 b, i64 &x, i64 &y) {
if (b == 0) {
x = 1;
y = 0;
return a;
}
i64 d = exGCD(b, a % b, y, x);
y -= a / b * x;
return d;
}
i64 exCRT(std::vector<i64> m, std::vector<i64> r) {
i64 ans = r[0], M = m[0], x, y;
for (int i = 1; i < m.size(); i++) {
i64 mi = m[i], t = ((r[i] - ans) % mi + mi) % mi;
i64 gcd = exGCD(M, mi, x, y);
if (t % gcd != 0) {
return -1;
}
x = gsc(x, t / gcd, mi);
ans += x * M;
M = mi / gcd * M;
ans = (ans % M + M) % M;
}
return ans;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
std::cin >> n;
std::vector<i64> a(n), b(n);
for (int i = 0; i < n; i++) {
std::cin >> a[i] >> b[i];
}
std::cout << exCRT(a, b) << "\n";
}
卢卡斯定理
例题1
给定整数 \(n, m, p\) 的值,求出 \(C_{n+m}^n \mod p\) 的值(扩展的区别在于模数是不是质数)。
#include <bits/stdc++.h>
using i64 = long long;
i64 ksm(i64 a, i64 b, i64 mod) {
i64 res = 1;
a %= mod;
while (b) {
if (b & 1) {
res = res * a % mod;
}
a = a * a % mod;
b >>= 1;
}
return res;
}
i64 exgcd(i64 a, i64 b, i64& x, i64& y) {
if (!b) {
x = 1;
y = 0;
return a;
}
i64 d = exgcd(b, a % b, y, x);
y -= a / b * x;
return d;
}
i64 inv(i64 a, i64 mod) {
i64 x, y;
exgcd(a, mod, x, y);
return (x + mod) % mod;
}
i64 f(i64 n, i64 p, i64 pk) {
if (!n) return 1;
i64 res = 1;
for (i64 i = 1; i <= pk; i++) {
if (i % p) {
res = res * i % pk;
}
}
res = ksm(res, n / pk, pk);
for (i64 i = n / pk * pk + 1; i <= n; i++) {
if (i % p) {
res = res * (i % pk) % pk;
}
}
return res * f(n / p, p, pk) % pk;
}
i64 g(i64 n, i64 p) {
i64 s = 0;
while (n) {
n /= p;
s += n;
}
return s;
}
i64 Cpk(i64 n, i64 m, i64 p, i64 pk) {
if (m < 0 || m > n) return 0;
i64 up = f(n, p, pk);
i64 down = inv(f(m, p, pk), pk) * inv(f(n - m, p, pk), pk) % pk;
i64 e = g(n, p) - g(m, p) - g(n - m, p);
return up * down % pk * ksm(p, e, pk) % pk;
}
i64 CRT(std::vector<i64>& m, std::vector<i64>& r) {
i64 M = 1, ans = 0;
for (auto v : m) {
M *= v;
}
for (int i = 0; i < m.size(); i++) {
i64 Mi = M / m[i];
ans = (ans + r[i] * Mi % M * inv(Mi, m[i])) % M;
}
return (ans + M) % M;
}
i64 exLucas(i64 n, i64 m, i64 mod) {
if (m < 0 || m > n) {
return 0;
}
std::vector<i64> P, R;
i64 tmp = mod;
for (i64 i = 2; i * i <= tmp; i++) {
if (tmp % i == 0) {
i64 pk = 1;
while (tmp % i == 0) {
tmp /= i, pk *= i;
}
P.push_back(pk);
R.push_back(Cpk(n, m, i, pk));
}
}
if (tmp > 1) {
P.push_back(tmp);
R.push_back(Cpk(n, m, tmp, tmp));
}
return CRT(P, R);
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
i64 n, m, mod;
std::cin >> n >> m >> mod;
std::cout << exLucas(n, m, mod) << "\n";
}
离散对数
例题1
给定 \(a, p, b\),求满足 \(a^x \equiv b \ (\text{mod} \ p)\) 的最小自然数 \(x\)(扩展的区别在于模数是不是质数)。
#include <bits/stdc++.h>
using i64 = long long;
const i64 INF = 4e18;
i64 ksm(i64 a, i64 n, i64 mod) {
i64 res = 1;
a = (a % mod + mod) % mod;
while (n) {
if (n & 1) {
res = (a * res) % mod;
}
a = (a * a) % mod;
n >>= 1;
}
return res;
}
i64 BSGS(i64 a, i64 b, i64 m, i64 k = 1) {
std::unordered_map<i64, i64> hash;
hash.clear();
i64 cur = 1, t = sqrt(m) + 1;
for (int B = 1; B <= t; B++) {
(cur *= a) %= m;
hash[b * cur % m] = B;
}
i64 now = cur * k % m;
for (int A = 1; A <= t; A++) {
auto it = hash.find(now);
if (it != hash.end()) {
return A * t - it->second;
}
(now *= cur) %= m;
}
return -INF;
}
i64 exBSGS(i64 a, i64 b, i64 m, i64 k = 1) {
i64 A = a %= m, B = b %= m, M = m;
if (b == 1) {
return 0;
}
i64 cur = 1 % m;
for (int i = 0;; i++) {
if (cur == B) {
return i;
}
cur = cur * A % M;
i64 d = std::__gcd(a, m);
if (b % d) {
return -INF;
}
if (d == 1) {
return BSGS(a, b, m, k * a % m) + i + 1;
}
k = k * a / d % m, b /= d, m /= d;
}
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int a, p, b;
while (std::cin >> a >> p >> b) {
if (!a && !p && !b) {
break;
}
i64 ans = exBSGS(a, b, p);
if (ans < 0) {
std::cout << "No Solution\n";
} else {
std::cout << ans << "\n";
}
}
}
莫比乌斯反演
例题1
#include <bits/stdc++.h>
using i64 = long long;
const i64 N = 5e4 + 10;
i64 primes[N], mu[N], pre[N], cnt = 1;
bool f[N];
void init() {
mu[1] = 1;
f[1] = true;
for (i64 i = 2; i <= N; i++) {
if (!f[i]) {
primes[cnt++] = i;
mu[i] = -1;
}
for (i64 j = 1; j <= cnt && i * primes[j] <= N; j++) {
f[i * primes[j]] = true;
if (i % primes[j] == 0) {
mu[i * primes[j]] = 0;
break;
}
mu[i * primes[j]] = -mu[i];
}
}
for (i64 i = 1; i <= N; i++) {
pre[i] = pre[i - 1] + mu[i];
}
}
i64 calc(int n, int m) {
i64 res = 0;
if (n > m) {
std::swap(n, m);
}
for (i64 l = 1, r; l <= n; l = r + 1) {
r = std::min(n / (n / l), m / (m / l));
res += (pre[r] - pre[l - 1]) * (n / l) * (m / l);
}
return res;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
init();
i64 tt;
std::cin >> tt;
while (tt--) {
i64 a, b, c, d, k;
std::cin >> a >> b >> c >> d >> k;
std::cout << calc(b / k, d / k) - calc(b / k, (c - 1) / k) - calc((a - 1) / k, d / k) + calc((a - 1) / k, (c - 1) / k) << "\n";
}
}
例题2
#include <bits/stdc++.h>
using i64 = long long;
const i64 MOD = 20101009;
const i64 N = 1e7 + 10;
i64 primes[N], mu[N], cnt = 1;
bool vis[N];
i64 sum[N];
void init() {
mu[1] = 1;
for (i64 i = 2; i < N; i++) {
if (!vis[i]) {
primes[cnt++] = i;
mu[i] = -1;
}
for (i64 j = 1; j <= cnt && i * primes[j] < N; j++) {
vis[i * primes[j]] = true;
if (i % primes[j] == 0) {
mu[i * primes[j]] = 0;
break;
}
mu[i * primes[j]] = -mu[i];
}
}
for (i64 i = 1; i < N; i++) {
sum[i] = (sum[i - 1] + i * i % MOD * (mu[i] + MOD)) % MOD;
}
}
i64 S(i64 x) {
return x * (x + 1) / 2 % MOD;
}
i64 calc(i64 n, i64 m) {
i64 res = 0;
for (i64 k = 1; k <= std::min(n, m); k++) {
i64 t = std::min(n / (n / k), m / (m / k));
i64 tmp = (sum[t] - sum[k - 1] + MOD) % MOD;
res = (res + tmp * S(n / k) % MOD * S(m / k) % MOD) % MOD;
k = t;
}
return res;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
init();
i64 n, m, ans = 0;
std::cin >> n >> m;
for (i64 d = 1; d <= std::min(n, m); d++) {
i64 t = std::min(n / (n / d), m / (m / d));
ans = (ans + (d + t) * (t - d + 1) / 2 % MOD * calc(n / d, m / d) % MOD) % MOD;
d = t;
}
std::cout << ans << "\n";
}
例题3
#include <bits/stdc++.h>
using i64 = long long;
const i64 N = 5e4 + 10;
i64 primes[N], mu[N], a[N], pre[N], cnt = 1;
bool f[N];
void init() {
mu[1] = 1;
f[1] = true;
for (i64 i = 2; i <= N; i++) {
if (!f[i]) {
primes[cnt++] = i;
mu[i] = -1;
}
for (i64 j = 1; j <= cnt && i * primes[j] <= N; j++) {
f[i * primes[j]] = true;
if (i % primes[j] == 0) {
mu[i * primes[j]] = 0;
break;
}
mu[i * primes[j]] = -mu[i];
}
}
for (i64 i = 1; i <= N; i++) {
for (i64 l = 1, r; l <= i; l = r + 1) {
r = i / (i / l);
a[i] += (r - l + 1) * (i / l);
}
}
for (i64 i = 1; i <= N; i++) {
pre[i] = pre[i - 1] + mu[i];
}
}
i64 calc(int n, int m) {
i64 res = 0;
if (n > m) {
std::swap(n, m);
}
for (i64 l = 1, r; l <= n; l = r + 1) {
r = std::min(n / (n / l), m / (m / l));
res += (pre[r] - pre[l - 1]) * a[n / l] * a[m / l];
}
return res;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
init();
i64 tt;
std::cin >> tt;
while (tt--) {
i64 n, m;
std::cin >> n >> m;
std::cout << calc(n, m) << "\n";
}
}
组合数学
#include <bits/stdc++.h>
using i64 = long long;
const int N = 1e6 + 10;
const int MOD = 1e9 + 7;
std::vector<i64> fac(N + 1, 1), invfac(N + 1, 1);
i64 ksm(i64 a, i64 n, i64 mod) {
i64 res = 1;
a = (a % mod + mod) % mod;
while (n) {
if (n & 1) {
res = (a * res) % mod;
}
a = (a * a) % mod;
n >>= 1;
}
return res;
}
void init(int n) {
fac[0] = 1;
for (int i = 1; i <= n; i++) {
fac[i] = fac[i - 1] * i % MOD;
}
invfac[n] = ksm(fac[n], MOD - 2, MOD);
for (int i = n - 1; i >= 0; i--) {
invfac[i] = invfac[i + 1] * (i + 1) % MOD;
}
}
i64 C(int n, int m) { // 组合数
if (m > n || m < 0) {
return 0;
}
return fac[n] * invfac[m] % MOD * invfac[n - m] % MOD;
}
i64 A(int n, int m) { // 排列数
if (m > n || m < 0) {
return 0;
}
return fac[n] * invfac[n - m] % MOD;
}
// n 对括号的合法匹配数,有 n 个节点的二叉树的种类数
// 从对角线下方走到对角线的路径数,栈的出栈序列数
i64 catalan(int n) { // 卡特兰数
if (n < 0) {
return 0;
}
return C(2 * n, n) * ksm(n + 1, MOD - 2, MOD) % MOD;
}
// 将 n 个不同的元素划分到 k 个非空集合中的方案数
i64 stirling2(int n, int k) { // 第二类斯特林数
if (k > n || k < 0) {
return 0;
}
i64 res = 0;
for (int i = 0; i <= k; i++) {
i64 term = C(k, i) * ksm(k - i, n, MOD) % MOD;
if (i % 2 == 1) {
term = (MOD - term) % MOD;
}
res = (res + term) % MOD;
}
return res * invfac[k] % MOD;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
init(N);
int n, m;
std::cin >> n >> m;
std::cout << stirling2(n, m) << "\n";
}
线性代数
线性基
例题1
n个数字任选最大化异或和。
#include <bits/stdc++.h>
using u64 = unsigned long long;
u64 p[64];
bool insert(u64 x) {
for (int i = 63; i >= 0; i--) {
if (!(x >> i)) {
continue;
}
if (!p[i]) {
p[i] = x;
return 1;
}
x ^= p[i];
}
return 0;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
u64 n, x, ans = 0;
std::cin >> n;
for (int i = 1; i <= n; i++) {
std::cin >> x;
insert(x);
}
for (int i = 63; i >= 0; i--) {
ans = std::max(ans, ans ^ p[i]);
}
std::cout << ans << "\n";
}
博弈论
Bash博弈
例题1
一堆物品,每次可以取 1 到 m 个,取到最后一个物品的人获胜,问先手能否必胜。
n能被m+1整除时先手必败。
#include <bits/stdc++.h>
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, m;
std::cin >> n >> m;
if (n % (m + 1)) {
std::cout << "YES\n";
} else {
std::cout << "NO\n";
}
}
Nim博弈
例题1
n 堆物品,每次可从一堆中取走至少一个物品,取到最后一个物品的人获胜,问先手能否必胜。
- 所有的物品数异或和为0时先手必败。
#include <bits/stdc++.h>
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, f = 0;
std::cin >> n;
for (int i = 0; i < n; i++) {
int x;
std::cin >> x;
f ^= x;
}
if (f) {
std::cout << "YES\n";
} else {
std::cout << "NO\n";
}
}
例题2
n堆物品,每次从不超过k堆中任取数量,取到最后一个物品的人获胜,问先手能否必胜。
- 所有物品的二进制每位上的1的总数能被
k+1整除时先手必败。
#include <bits/stdc++.h>
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, k, f = 0;
std::cin >> n >> k;
std::vector<int> bin;
for (int i = 0; i < n; i++) {
int x;
std::cin >> x;
for (int j = 0; j < 31; j++) {
bin[j] += x & (1 << j);
}
}
for (int i = 0; i < 31; i++) {
if (bin[i] % (k + 1)) {
f = 1;
break;
}
}
if (f) {
std::cout << "YES\n";
} else {
std::cout << "NO\n";
}
}
例题3
n堆物品,每次可从一堆中取走至少一个物品,取到最后一个物品的人失败,问先手能否必胜,John先手Brother后手。
- 所有物品数只有1且异或和为0,或有一堆多于1个且异或和不为0时先手必胜。
#include <bits/stdc++.h>
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int tt;
std::cin >> tt;
while (tt--) {
int n, cnt = 0, f = 0;
std::cin >> n;
for (int i = 0; i < n; i++) {
int x;
std::cin >> x;
f ^= x;
cnt += x == 1;
}
if (cnt == n) {
if (n % 2) {
std::cout << "Brother\n";
} else {
std::cout << "John\n";
}
} else {
if (f) {
std::cout << "John\n";
} else {
std::cout << "Brother\n";
}
}
}
}
例题4
n堆物品,每次可从第k堆中取走至少一个物品转移到第k-1堆,第0堆不能转移,无法操作的人失败,问先手能否必胜。
- 奇数堆中的石子数异或和为0时先手必败。
#include <bits/stdc++.h>
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, f = 0;
std::cin >> n;
for (int i = 0; i < n; i++) {
int x;
std::cin >> x;
if (i % 2 == 0) {
f ^= x;
}
}
if (f) {
std::cout << "YES\n";
} else {
std::cout << "NO\n";
}
}
Wythoff博弈
例题1
两堆物品,可以在一堆中取任意数量或在两堆取相同数量,取到最后一个物品的人获胜,问先手能否必胜。
- 较少堆数量与两堆差值为黄金比(\(\phi = \frac{1 + \sqrt{5}}{2}\))时先手必败。
from decimal import *
a, b = map(int, input().split())
phi = (Decimal(1) + Decimal(5).sqrt()) / Decimal(2)
print(int(min(a, b) != int(phi * Decimal(abs(a - b)))))
斐波那契博弈
例题1
一堆物品,每次至少取1个且不超过上次取的两倍,取走最后一个物品的人获胜,问先手能否必胜。
- n是斐波那契数时先手必败。
#include <bits/stdc++.h>
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
while (std::cin >> n) {
if (!n) {
break;
}
int a = 1, b = 1;
while (b < n) {
int c = a;
a += b;
b = c;
}
if (b != n) {
std::cout << "First win\n";
} else {
std::cout << "Second win\n";
}
}
}
SG函数
博弈的步骤可以变成一个n点m边的有向无环图,问先手能否必胜。
- 根据sg定理算出所有根节点的sg值求异或和是否为零判断先手能否必胜。
例题1
两个正整数,每次操作可以从较大的数减去较小数的正整数倍,直到其中一个数变为0,问先手能否必胜,Stan先手Ollie后手。
#include <bits/stdc++.h>
std::map<std::pair<int, int>, int> sg;
int getsg(int n, int m) {
if (n < m) {
std::swap(n, m);
}
if (m == 0) {
return 0;
}
if (sg.count({n, m})) {
return sg[{n, m}];
}
std::set<int> mex;
for (int i = 1; i * m <= n; i++) {
mex.insert(getsg(n - i * m, m));
}
for (int i = 0;; i++) {
if (!mex.count(i)) {
return sg[{n, m}] = i;
}
}
}
int main() {
std::ios_base::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int c;
std::cin >> c;
while (c--) {
int n, m;
std::cin >> n >> m;
if (getsg(n, m)) {
std::cout << "Stan wins\n";
} else {
std::cout << "Ollie wins\n";
}
}
}
数值算法
高斯约旦消元
例题1
#include <bits/stdc++.h>
const double EPS = 1e-6;
int Gauss_Jordan(int n, std::vector<std::vector<double>> &a) {
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j++) {
if (fabs(a[j][i]) > EPS) {
std::swap(a[i], a[j]);
break;
}
}
if (fabs(a[i][i]) < EPS) {
return 0;
}
for (int j = 1; j <= n; j++) {
if (i != j) {
for (int k = n + 1; k >= i; k--) {
a[j][k] -= a[j][i] / a[i][i] * a[i][k];
}
}
}
}
for (int i = 1; i <= n; i++) {
a[i][n + 1] /= a[i][i];
}
return 1;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
std::cin >> n;
std::vector<std::vector<double>> a(n + 2, std::vector<double>(n + 2));
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n + 1; j++) {
std::cin >> a[i][j];
}
}
if (Gauss_Jordan(n, a)) {
for (int i = 1; i <= n; i++) {
std::cout << std::fixed << std::setprecision(2) << a[i][n + 1] << "\n";
}
} else {
std::cout << "No Solution" << "\n";
}
}
矩阵求逆
例题1
求一个N×N的矩阵的逆矩阵。
#include <bits/stdc++.h>
using i64 = long long;
const int MOD = 1e9 + 7;
i64 ksm(i64 a, i64 n, i64 mod) {
i64 res = 1;
a = (a % mod + mod) % mod;
while (n) {
if (n & 1) {
res = (a * res) % mod;
}
a = (a * a) % mod;
n >>= 1;
}
return res;
}
int Gauss_Jordan(int n, std::vector<std::vector<i64>> &a) {
for (int i = 1; i <= n; i++) {
int r = i;
for (int k = i; k <= n; k++) {
if (a[k][i]) {
std::swap(a[i], a[k]);
break;
}
}
if (!a[i][i]) {
return 0;
}
i64 inv = ksm(a[i][i], MOD - 2, MOD);
for (int k = 1; k <= n; k++) {
if (k != i) {
i64 t = a[k][i] * inv % MOD;
for (int j = i; j <= 2 * n; j++) {
a[k][j] = (a[k][j] - t * a[i][j] % MOD + MOD) % MOD;
}
}
}
for (int j = 1; j <= 2 * n; j++) {
a[i][j] = a[i][j] * inv % MOD;
}
}
return 1;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
std::cin >> n;
std::vector<std::vector<i64>> a(n + 1, std::vector<i64>(2 * (n + 1)));
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
std::cin >> a[i][j];
a[i][i + n] = 1;
}
}
if (Gauss_Jordan(n, a)) {
for (int i = 1; i <= n; i++) {
for (int j = n + 1; j <= 2 * n; j++) {
std::cout << a[i][j] << " \n"[j == 2 * n];
}
}
} else {
std::cout << "No Solution" << "\n";
}
}
图论
拓扑排序
例题1
给一个有向无环图的所有节点排序。
#include <bits/stdc++.h>
using i64 = long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, m;
std::cin >> n >> m;
std::vector<std::vector<int>> g(n + 1);
std::vector<int> in(n + 1), order;
std::queue<int> q;
for (int i = 1; i <= m; i++) {
int u, v;
std::cin >> u >> v;
g[u].push_back(v);
in[v]++;
}
for (int i = 1; i <= n; i++) {
if (in[i] == 0) {
q.push(i);
}
}
while (!q.empty()) {
int u = q.front();
q.pop();
order.push_back(u);
for (auto v : g[u]) {
in[v]--;
if (in[v] == 0) {
q.push(v);
}
}
}
for (auto x : order) {
std::cout << x << " \n"[x == order.back()];
}
}
最短路
给定n个点m条带权边,求指定两点间最短距离。
Floyd
例题1
求两点间最短路。
#include <bits/stdc++.h>
using i64 = long long;
const i64 INF = 4e18;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, m, q;
std::cin >> n >> m >> q;
std::vector<std::vector<i64>> g(n + 1, std::vector<i64>(n + 1, INF));
for (int i = 1; i <= n; i++) {
g[i][i] = 0;
}
for (int i = 1; i <= m; i++) {
i64 u, v, w;
std::cin >> u >> v >> w;
g[u][v] = std::min(g[u][v], w);
}
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
g[i][j] = std::min(g[i][j], g[i][k] + g[k][j]);
}
}
}
while (q--) {
i64 u, v;
std::cin >> u >> v;
if (g[u][v] > INF / 2) {
std::cout << "impossible" << "\n";
} else {
std::cout << g[u][v] << "\n";
}
}
}
Dijkstra
例题1
求两点间最短路。
#include <bits/stdc++.h>
using i64 = long long;
const int N = 2e5 + 10;
const i64 INF = 4e18;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, m;
std::cin >> n >> m;
std::vector<std::vector<std::array<i64, 2>>> g(n + 1);
for (int i = 1; i <= m; i++) {
i64 u, v, w;
std::cin >> u >> v >> w;
if (u != v) {
g[u].push_back({v, w});
}
}
std::vector<i64> dist(n + 1, INF), vis(n + 1);
std::priority_queue<std::array<i64, 2>, std::vector<std::array<i64, 2>>, std::greater<std::array<i64, 2>>> pq;
dist[1] = 0;
pq.push({0, 1});
while (!pq.empty()) {
auto [d, u] = pq.top();
pq.pop();
if (vis[u]) {
continue;
}
vis[u] = 1;
for (auto [v, w] : g[u]) {
if (!vis[v] && dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
pq.push({dist[v], v});
}
}
}
if (dist[n] != INF) {
std::cout << dist[n] << "\n";
} else {
std::cout << -1 << "\n";
}
}
Bellman_Ford
例题1
最多经过k条边,可能有负边权。
#include <bits/stdc++.h>
using i64 = long long;
const i64 INF = 4e18;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, m, k;
std::cin >> n >> m >> k;
std::vector<std::array<i64, 3>> edges;
for (int i = 1; i <= m; i++) {
i64 u, v, w;
std::cin >> u >> v >> w;
edges.push_back({u, v, w});
}
std::vector<i64> dist(n + 1, INF), last(n + 1);
dist[1] = 0;
for (int i = 0; i < k; i++) {
last = dist;
for (auto [u, v, w] : edges) {
if (last[u] + w < dist[v]) {
dist[v] = last[u] + w;
}
}
}
if (dist[n] > INF / 2) {
std::cout << "impossible\n";
} else {
std::cout << dist[n] << "\n";
}
}
例题2
检测负环。
#include <bits/stdc++.h>
using i64 = long long;
const i64 INF = 4e18;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int tt;
std::cin >> tt;
while (tt--) {
int n, m, f = 0;
std::cin >> n >> m;
std::vector<std::vector<std::array<i64, 2>>> g(n + 1);
for (int i = 1; i <= m; i++) {
i64 u, v, w;
std::cin >> u >> v >> w;
if (w >= 0) {
g[v].push_back({u, w});
}
g[u].push_back({v, w});
}
std::vector<i64> dist(n + 1, INF);
dist[1] = 0;
for (int i = 0; i < n; i++) {
for (int u = 1; u <= n; u++) {
if (dist[u] == INF) {
continue;
}
for (auto [v, w] : g[u]) {
if (dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
if (i == n - 1) {
f = 1;
}
}
}
}
}
if (f) {
std::cout << "YES\n";
} else {
std::cout << "NO\n";
}
}
}
Spfa
可能有负边权。
例题1
求两点间最短路。
#include <bits/stdc++.h>
using i64 = long long;
const i64 INF = 4e18;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, m;
std::cin >> n >> m;
std::vector<std::vector<std::array<i64, 2>>> g(n + 1);
for (int i = 1; i <= m; i++) {
i64 u, v, w;
std::cin >> u >> v >> w;
g[u].push_back({v, w});
}
std::vector<i64> dist(n + 1, INF), vis(n + 1);
dist[1] = 0;
std::queue<int> q;
q.push(1);
vis[1] = 1;
while (!q.empty()) {
int u = q.front();
q.pop();
vis[u] = 0;
for (auto [v, w] : g[u]) {
if (dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
if (!vis[v]) {
q.push(v);
vis[v] = 1;
}
}
}
}
if (dist[n] > INF / 2) {
std::cout << "impossible\n";
} else {
std::cout << dist[n] << "\n";
}
}
例题2
检测负环。
#include <bits/stdc++.h>
using i64 = long long;
const i64 INF = 4e18;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, m;
std::cin >> n >> m;
std::vector<std::vector<std::array<i64, 2>>> g(n + 1);
for (int i = 1; i <= m; i++) {
i64 u, v, w;
std::cin >> u >> v >> w;
g[u].push_back({v, w});
}
std::vector<i64> dist(n + 1), cnt(n + 1), vis(n + 1);
std::queue<int> q;
for (int i = 1; i <= n; i++) {
q.push(i);
vis[i] = 1;
}
int f = 0;
while (!q.empty() && !f) {
int u = q.front();
q.pop();
vis[u] = 0;
for (auto [v, w] : g[u]) {
if (dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
cnt[v] = cnt[u] + 1;
if (cnt[v] >= n) {
f = 1;
break;
}
if (!vis[v]) {
q.push(v);
vis[v] = 1;
}
}
}
}
if (f) {
std::cout << "Yes";
} else {
std::cout << "No";
}
}
差分约束
例题1
多个 \(x_a - x_b \leq y_c\) 不等式组求解。
#include <bits/stdc++.h>
using i64 = long long;
const i64 INF = 4e18;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, m;
std::cin >> n >> m;
std::vector<i64> dist(n + 1, INF), vis(n + 1), in(n + 1);
std::vector<std::vector<std::array<i64, 2>>> g(n + 1, std::vector<std::array<i64, 2>>());
for (int i = 1; i <= m; i++) {
i64 u, v, w;
std::cin >> u >> v >> w;
g[v].push_back({u, w});
}
for (int i = 1; i <= n; i++) {
g[0].push_back({i, 0});
}
dist[0] = 0;
std::queue<i64> q;
q.push(0);
vis[0] = 1;
in[0] = 1;
while (!q.empty()) {
i64 u = q.front();
q.pop();
vis[u] = 0;
for (auto [v, w] : g[u]) {
if (dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
if (!vis[v]) {
q.push(v);
vis[v] = 1;
in[v]++;
if (in[v] > n + 1) {
std::cout << "NO";
return 0;
}
}
}
}
}
if (dist[n] > INF / 2) {
std::cout << "NO";
} else {
for (int i = 1; i <= n; i++) {
std::cout << dist[i] << " \n"[i == n];
}
}
}
最小生成树
边权和最小的生成树。
例题1
求一颗边权和最小的生成树。
// Kruskal,从小到大加入边
#include <bits/stdc++.h>
using i64 = long long;
struct DSU {
int cnt;
std::vector<int> fa, rank, siz;
DSU(int n) : cnt(n), fa(n + 1), rank(n + 1), siz(n + 1, 1) {
for (int i = 1; i <= n; i++) {
fa[i] = i;
}
}
int find(int u) {
if (fa[u] != u) {
fa[u] = find(fa[u]);
}
return fa[u];
}
void merge(int u, int v) {
int U = find(u), V = find(v);
if (U != V) {
if (rank[U] >= rank[V]) {
fa[V] = U;
siz[U] += siz[V];
if (rank[U] == rank[V]) {
rank[U]++;
}
} else {
fa[U] = V;
siz[V] += siz[U];
}
cnt--;
}
}
int size() {
return cnt;
}
int count(int u) {
return siz[find(u)];
}
bool connected(int u, int v) {
return find(u) == find(v);
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
i64 n, m, ans = 0;
std::cin >> n >> m;
DSU dsu(n);
std::vector<std::array<int, 3>> g(m);
for (int i = 0; i < m; i++) {
int u, v, w;
std::cin >> u >> v >> w;
g[i] = {w, u, v};
}
std::sort(g.begin(), g.end());
for (auto [w, u, v] : g) {
if (dsu.find(u) != dsu.find(v)) {
ans += w;
dsu.merge(u, v);
}
}
if (dsu.size() == 1) {
std::cout << ans << "\n";
} else {
std::cout << -1 << "\n";
}
}
// Prim,从一个结点开始不断加点
#include <bits/stdc++.h>
using i64 = long long;
const i64 INF = 4e18;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, m;
std::cin >> n >> m;
std::vector<std::vector<std::array<i64, 2>>> g(n + 1);
for (int i = 0; i < m; i++) {
i64 u, v, w;
std::cin >> u >> v >> w;
g[u].push_back({v, w});
g[v].push_back({u, w});
}
std::vector<i64> dist(n + 1, INF), intree(n + 1);
i64 ans = 0;
std::priority_queue<std::array<i64, 2>, std::vector<std::array<i64, 2>>, std::greater<>> pq;
dist[1] = 0;
pq.push({0, 1});
while (!pq.empty()) {
auto [d, u] = pq.top();
pq.pop();
if (intree[u]) {
continue;
}
intree[u] = 1;
ans += d;
for (auto [v, w] : g[u]) {
if (!intree[v] && w < dist[v]) {
dist[v] = w;
pq.push({w, v});
}
}
}
int f = 1;
for (int i = 1; i <= n; i++) {
if (!intree[i]) {
f = 0;
}
}
if (f) {
std::cout << ans << "\n";
} else {
std::cout << -1 << "\n";
}
}
二分图
节点由两个集合组成,且两个集合内部没有边的图。
最大匹配
例题1
选出一些边,使得这些边没有公共顶点,且边的数量最大。
// Hungarian
#include <bits/stdc++.h>
const int N = 5e2 + 10;
std::vector<int> g[N], vis(N), match(N);
int find(int u) {
for (auto v : g[u]) {
if (!vis[v]) {
vis[v] = 1;
if (!match[v] || find(match[v])) {
match[v] = u;
return 1;
}
}
}
return 0;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n1, n2, m, ans = 0;
std::cin >> n1 >> n2 >> m;
for (int i = 0; i < m; i++) {
int u, v;
std::cin >> u >> v;
g[u].push_back(v);
}
for (int i = 1; i <= n1; i++) {
std::fill(vis.begin(), vis.end(), 0);
ans += find(i);
}
std::cout << ans << "\n";
}
联通性相关
tarjan
例题1
给一张图,求割点
#include <bits/stdc++.h>
using i64 = long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, m;
std::cin >> n >> m;
std::vector<std::vector<int>> g(n + 1, std::vector<int>());
for (int i = 0; i < m; i++) {
int u, v;
std::cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
int dfncnt = 0;
std::vector<int> cut(n + 1), dfn(n + 1), low(n + 1);
auto tarjan = [&](auto &&self, int u, int root) -> void {
dfn[u] = low[u] = ++dfncnt;
int child = 0;
for (auto v : g[u]) {
if (!dfn[v]) {
self(self, v, root);
low[u] = std::min(low[u], low[v]);
if (low[v] >= dfn[u] && u != root) {
cut[u] = 1;
}
if (u == root) {
child++;
}
} else
low[u] = std::min(low[u], dfn[v]);
}
if (child >= 2 && u == root) {
cut[u] = 1;
}
};
for (int i = 1; i <= n; i++) {
if (!dfn[i]) {
tarjan(tarjan, i, i);
}
}
std::cout << std::count(cut.begin(), cut.end(), 1) << "\n";
for (int i = 1; i <= n; i++) {
if (cut[i]) {
std::cout << i << " \n"[i == n];
}
}
}
例题2
给一张图,求割边
#include <bits/stdc++.h>
using i64 = long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, m;
std::cin >> n >> m;
std::vector<std::vector<int>> g(n + 1, std::vector<int>());
for (int i = 0; i < m; i++) {
int u, v;
std::cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
int dfncnt = 0;
std::vector<int> dfn(n + 1), low(n + 1);
std::vector<std::array<int, 2>> cut;
auto tarjan = [&](auto &&self, int u, int fa) -> void {
dfn[u] = low[u] = ++dfncnt;
for (auto v : g[u]) {
if (v == fa) {
continue;
}
if (!dfn[v]) {
self(self, v, u);
low[u] = std::min(low[u], low[v]);
if (low[v] > dfn[u]) {
cut.push_back({std::min(u, v), std::max(u, v)});
}
} else if (u != fa) {
low[u] = std::min(low[u], dfn[v]);
}
}
};
for (int i = 1; i <= n; i++) {
if (!dfn[i]) {
tarjan(tarjan, i, i);
}
}
std::cout << cut.size() << "\n";
for (auto [u, v] : cut) {
std::cout << u << " " << v << "\n";
}
}
树上问题
最近公共祖先
例题1
给一颗树,多次询问两点LCA。
// 倍增
#include <bits/stdc++.h>
using i64 = long long;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, m, s;
std::cin >> n >> m >> s;
int LOG = std::log2(n) + 1;
std::vector<std::vector<int>> fa(n + 1, std::vector<int>(LOG + 1)), g(n + 1);
std::vector<int> dep(n + 1);
for (int i = 1; i < n; i++) {
int u, v;
std::cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
std::queue<int> q;
fa[s][0] = s;
dep[s] = 0;
q.push(s);
while (!q.empty()) {
auto u = q.front();
q.pop();
for (int i = 1; i <= LOG; i++) {
fa[u][i] = fa[fa[u][i - 1]][i - 1];
}
for (auto v : g[u]) {
if (v != fa[u][0]) {
fa[v][0] = u;
dep[v] = dep[u] + 1;
q.push(v);
}
}
}
auto LCA = [&](int u, int v) -> int {
if (dep[u] < dep[v]) {
std::swap(u, v);
}
for (int i = LOG; i >= 0; i--) {
if (dep[fa[u][i]] >= dep[v]) {
u = fa[u][i];
}
}
if (u == v) {
return u;
}
for (int i = LOG; i >= 0; i--) {
if (fa[u][i] != fa[v][i]) {
u = fa[u][i];
v = fa[v][i];
}
}
return fa[u][0];
};
for (int i = 0; i < m; i++) {
int u, v;
std::cin >> u >> v;
std::cout << LCA(u, v) << "\n";
}
}
数据结构
树状数组
例题1
区间修改,区间查询。
#include <bits/stdc++.h>
using i64 = long long;
struct FenwickTree { // 1-index [l,r]
int n;
std::vector<i64> tree1, tree2;
FenwickTree(int n) : n(n), tree1(n + 1), tree2(n + 1) {
}
int lowbit(int x) {
return x & -x;
}
void add(std::vector<i64>& tree, int pos, i64 x) {
for (int i = pos; i <= n; i += lowbit(i)) {
tree[i] += x;
}
}
void range_add(int l, int r, i64 x) {
add(tree1, l, x);
add(tree1, r + 1, -x);
add(tree2, l, x * (l - 1));
add(tree2, r + 1, -x * r);
}
i64 ask(std::vector<i64>& tree, int pos) {
i64 res = 0;
for (int i = pos; i > 0; i -= lowbit(i)) {
res += tree[i];
}
return res;
}
i64 range_ask(int l, int r) {
i64 resr = ask(tree1, r) * r - ask(tree2, r);
i64 resl = ask(tree1, l - 1) * (l - 1) - ask(tree2, l - 1);
return resr - resl;
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, m;
std::cin >> n >> m;
FenwickTree ft(n);
for (int i = 1; i <= n; i++) {
i64 p;
std::cin >> p;
ft.range_add(i, i, p);
}
while (m--) {
int op;
std::cin >> op;
if (op == 1) {
i64 l, r, x;
std::cin >> l >> r >> x;
ft.range_add(l, r, x);
} else if (op == 2) {
int l, r;
std::cin >> l;
r = l;
std::cout << ft.range_ask(l, r) << "\n";
}
}
}
例题2
树状数组求逆序对。
#include <bits/stdc++.h>
using i64 = long long;
struct FenwickTree {
int n;
std::vector<i64> tree1, tree2;
FenwickTree(int n) : n(n), tree1(n + 1), tree2(n + 1) {
}
int lowbit(int x) {
return x & -x;
}
void add(std::vector<i64>& tree, int pos, i64 x) {
for (int i = pos; i <= n; i += lowbit(i)) {
tree[i] += x;
}
}
void range_add(int l, int r, i64 x) {
add(tree1, l, x);
add(tree1, r + 1, -x);
add(tree2, l, x * (l - 1));
add(tree2, r + 1, -x * r);
}
i64 ask(std::vector<i64>& tree, int pos) {
i64 res = 0;
for (int i = pos; i > 0; i -= lowbit(i)) {
res += tree[i];
}
return res;
}
i64 range_ask(int l, int r) {
i64 resr = ask(tree1, r) * r - ask(tree2, r);
i64 resl = ask(tree1, l - 1) * (l - 1) - ask(tree2, l - 1);
return resr - resl;
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
std::cin >> n;
std::vector<i64> a(n + 1), idx(n + 1);
for (int i = 1; i <= n; i++) {
std::cin >> a[i];
idx[i] = i;
}
std::sort(idx.begin() + 1, idx.begin() + n + 1, [&](int x, int y) {
if (a[x] == a[y]) {
return x > y;
}
return a[x] > a[y];
});
FenwickTree ft(n);
i64 ans = 0;
for (int i = 1; i <= n; i++) {
ft.range_add(idx[i], idx[i], 1);
ans += ft.range_ask(1, idx[i] - 1);
}
std::cout << ans << "\n";
}
并查集
普通并查集
例题1
判断联通性。
#include <bits/stdc++.h>
using i64 = long long;
struct DSU { // 1-index
int cnt;
std::vector<int> fa, rank, siz;
DSU(int n) : cnt(n), fa(n + 1), rank(n + 1), siz(n + 1, 1) {
for (int i = 1; i <= n; i++) {
fa[i] = i;
}
}
int find(int u) {
if (fa[u] != u) {
fa[u] = find(fa[u]);
}
return fa[u];
}
void merge(int u, int v) {
int U = find(u), V = find(v);
if (U != V) {
if (rank[U] >= rank[V]) {
fa[V] = U;
siz[U] += siz[V];
if (rank[U] == rank[V]) {
rank[U]++;
}
} else {
fa[U] = V;
siz[V] += siz[U];
}
cnt--;
}
}
int size() {
return cnt;
}
int count(int u) {
return siz[find(u)];
}
bool connected(int u, int v) {
return find(u) == find(v);
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, m;
std::cin >> n >> m;
DSU dsu(n);
for (int i = 1; i <= m; i++) {
int op, u, v;
std::cin >> op >> u >> v;
if (op == 1) {
dsu.merge(u, v);
} else {
if (dsu.conncted(u, v)) {
std::cout << "Y\n";
} else {
std::cout << "N\n";
}
}
}
}
带权并查集
例题1
n个动物,3种级别,k条关系,1XY表示XY是同级,2XY表示X吃Y
- 与真话冲突的话是假话
- 动物编号超过n是假话
- 同级别的互吃是假话
问有多少假话
#include <bits/stdc++.h>
using i64 = long long;
struct WeightedDSU { // 1-index
int cnt;
std::vector<int> fa, rank, siz, w;
WeightedDSU(int n) : cnt(n), fa(n + 1), rank(n + 1), siz(n + 1, 1), w(n + 1) {
for (int i = 1; i <= n; i++) {
fa[i] = i;
}
}
int find(int u) {
if (fa[u] != u) {
int f = fa[u];
fa[u] = find(f);
w[u] += w[f];
}
return fa[u];
}
bool merge(int u, int v, i64 delta) {
int U = find(u), V = find(v);
if (U == V) {
return (w[v] - w[u] == delta);
}
if (rank[U] < rank[V]) {
std::swap(U, V);
std::swap(u, v);
delta = -delta;
}
fa[V] = U;
w[V] = delta + w[u] - w[v];
siz[U] += siz[V];
if (rank[U] == rank[V]) {
rank[U]++;
}
cnt--;
return true;
}
bool connected(int u, int v) {
return find(u) == find(v);
}
i64 diff(int u, int v) {
if (!connected(u, v)) {
return 0;
}
return w[v] - w[u];
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, k;
std::cin >> n >> k;
WeightedDSU dsu(n);
int ans = 0;
for (int i = 0; i < k; i++) {
int op, u, v, delta;
std::cin >> op >> u >> v;
if (u > n || v > n || op == 2 && u == v) {
ans++;
continue;
}
if (op == 1) {
delta = 0;
} else {
delta = 2;
}
int U = dsu.find(u), V = dsu.find(v);
if (U == V) {
if (((dsu.diff(u, v) - delta) % 3 + 3) % 3) {
ans++;
}
} else {
dsu.merge(u, v, ((dsu.diff(u, v) + delta) % 3 + 3) % 3);
}
}
std::cout << ans << "\n";
}
可撤销并查集
例题1
- 1xy联通集合xy
- 2xy查询集合xy是否联通
- 3v回溯到操作v,下一次从v+1开始
#include <bits/stdc++.h>
using i64 = long long;
struct RollbackDSU {
int cnt;
std::vector<int> fa, rank, siz;
std::vector<std::array<int, 5>> stk;
RollbackDSU(int n) : cnt(n), fa(n + 1), rank(n + 1), siz(n + 1, 1) {
for (int i = 1; i <= n; i++) {
fa[i] = i;
}
}
int find(int u) {
while (u != fa[u]) {
u = fa[u]; // 不能路径压缩
}
return u;
}
void merge(int u, int v, int id) {
int U = find(u), V = find(v);
if (U != V) {
int f = 0;
if (rank[U] < rank[V]) {
std::swap(U, V);
}
if (rank[U] == rank[V]) {
rank[U]++;
f = 1;
}
stk.push_back({U, V, id, f, siz[V]});
fa[V] = U;
siz[U] += siz[V];
cnt--;
}
}
void rollback(int oldid) {
while (!stk.empty()) {
auto [u, v, id, f, sv] = stk.back();
if (id <= oldid) {
return;
}
stk.pop_back();
fa[v] = v;
siz[u] -= sv;
siz[v] = sv;
if (f) {
rank[u]--;
}
cnt++;
}
}
int size() {
return cnt;
}
int count(int u) {
return siz[find(u)];
}
bool connected(int u, int v) {
return find(u) == find(v);
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, q;
std::cin >> n >> q;
RollbackDSU dsu(n);
std::string ans;
int id = 0;
for (int i = 1; i <= q; i++) {
id++;
int op;
std::cin >> op;
if (op == 1) {
int u, v;
std::cin >> u >> v;
dsu.merge(u, v, id);
} else if (op == 2) {
int u, v;
std::cin >> u >> v;
ans.push_back('0' + dsu.connected(u, v));
} else {
std::cin >> id;
dsu.rollback(id);
}
}
std::cout << ans << "\n";
}
线段树
普通线段树
例题1
区间加,区间乘,区间查询。
#include <bits/stdc++.h>
using i64 = long long;
template <bool use>
struct SegmentTree {
struct Node {
int l, r;
i64 sum, add, mul;
};
i64 size, mod = 0;
std::vector<Node> tree;
SegmentTree(int n, i64 mod = 0) : size(n) {
tree.resize(4 * size + 4);
for (auto &node : tree) {
node.mul = 1;
node.add = 0;
node.sum = 0;
node.l = 0;
node.r = 0;
}
if (use) {
this->mod = mod;
}
}
void build(int pos, int l, int r, std::vector<i64> &a) {
tree[pos].l = l;
tree[pos].r = r;
tree[pos].mul = 1;
if (l == r) {
tree[pos].sum = a[l];
if (use) {
tree[pos].sum %= mod;
}
return;
}
int mid = (l + r) / 2;
build(pos * 2, l, mid, a);
build(pos * 2 + 1, mid + 1, r, a);
update(pos);
}
void update(int pos) {
tree[pos].sum = tree[pos * 2].sum + tree[pos * 2 + 1].sum;
if (use) {
tree[pos].sum %= mod;
}
}
void pushdown(int pos) {
int l = pos * 2, r = pos * 2 + 1;
tree[l].sum = tree[l].sum * tree[pos].mul + tree[pos].add * (tree[l].r - tree[l].l + 1);
tree[r].sum = tree[r].sum * tree[pos].mul + tree[pos].add * (tree[r].r - tree[r].l + 1);
if (use) {
tree[l].sum %= mod;
tree[r].sum %= mod;
}
tree[l].mul = tree[l].mul * tree[pos].mul;
tree[r].mul = tree[r].mul * tree[pos].mul;
if (use) {
tree[l].mul %= mod;
tree[r].mul %= mod;
}
tree[l].add = tree[l].add * tree[pos].mul + tree[pos].add;
tree[r].add = tree[r].add * tree[pos].mul + tree[pos].add;
if (use) {
tree[l].add %= mod;
tree[r].add %= mod;
}
tree[pos].mul = 1;
tree[pos].add = 0;
}
void range_mul(int pos, int l, int r, i64 k) {
if (l <= tree[pos].l && tree[pos].r <= r) {
tree[pos].sum = tree[pos].sum * k;
tree[pos].mul = tree[pos].mul * k;
tree[pos].add = tree[pos].add * k;
if (use) {
tree[pos].sum %= mod;
tree[pos].mul %= mod;
tree[pos].add %= mod;
}
return;
}
pushdown(pos);
int mid = (tree[pos].l + tree[pos].r) / 2;
if (l <= mid) {
range_mul(pos * 2, l, r, k);
}
if (r > mid) {
range_mul(pos * 2 + 1, l, r, k);
}
update(pos);
}
void range_add(int pos, int l, int r, i64 k) {
if (l <= tree[pos].l && tree[pos].r <= r) {
tree[pos].sum = tree[pos].sum + k * (tree[pos].r - tree[pos].l + 1);
tree[pos].add = tree[pos].add + k;
if (use) {
tree[pos].sum %= mod;
tree[pos].add %= mod;
}
return;
}
pushdown(pos);
int mid = (tree[pos].l + tree[pos].r) / 2;
if (l <= mid) {
range_add(pos * 2, l, r, k);
}
if (r > mid) {
range_add(pos * 2 + 1, l, r, k);
}
update(pos);
}
i64 range_query(int pos, int l, int r) {
if (l <= tree[pos].l && tree[pos].r <= r) {
return tree[pos].sum;
}
pushdown(pos);
i64 res = 0;
int mid = (tree[pos].l + tree[pos].r) / 2;
if (l <= mid) {
res = res + range_query(pos * 2, l, r);
}
if (r > mid) {
res = res + range_query(pos * 2 + 1, l, r);
}
if (use) {
res %= mod;
}
return res;
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
i64 n, m, mod;
std::cin >> n >> m >> mod;
std::vector<i64> a(n + 1);
for (int i = 1; i <= n; i++) {
std::cin >> a[i];
}
SegmentTree<true> seg(n, mod);
seg.build(1, 1, n, a);
for (int i = 0; i < m; i++) {
i64 op;
std::cin >> op;
if (op == 1) {
int x, y, k;
std::cin >> x >> y >> k;
seg.range_mul(1, x, y, k);
} else if (op == 2) {
int x, y, k;
std::cin >> x >> y >> k;
seg.range_add(1, x, y, k);
} else if (op == 3) {
int x, y;
std::cin >> x >> y;
std::cout << seg.range_query(1, x, y) << "\n";
}
}
}
ST表 && RMQ
例题1
区间最值。
// 状压RMQ
#include <bits/stdc++.h>
template <class T, class Cmp = std::less<T>>
struct RMQ {
const Cmp cmp = Cmp();
static constexpr unsigned B = 64;
using u64 = unsigned long long;
int n;
std::vector<std::vector<T>> a;
std::vector<T> pre, suf, ini;
std::vector<u64> stk;
RMQ() {
}
RMQ(const std::vector<T> &v) {
init(v);
}
void init(const std::vector<T> &v) {
n = v.size();
pre = suf = ini = v;
stk.resize(n);
if (!n) {
return;
}
const int M = (n - 1) / B + 1;
const int lg = std::__lg(M);
a.assign(lg + 1, std::vector<T>(M));
for (int i = 0; i < M; i++) {
a[0][i] = v[i * B];
for (int j = 1; j < B && i * B + j < n; j++) {
a[0][i] = std::min(a[0][i], v[i * B + j], cmp);
}
}
for (int i = 1; i < n; i++) {
if (i % B) {
pre[i] = std::min(pre[i], pre[i - 1], cmp);
}
}
for (int i = n - 2; i >= 0; i--) {
if (i % B != B - 1) {
suf[i] = std::min(suf[i], suf[i + 1], cmp);
}
}
for (int j = 0; j < lg; j++) {
for (int i = 0; i + (2 << j) <= M; i++) {
a[j + 1][i] = std::min(a[j][i], a[j][i + (1 << j)], cmp);
}
}
for (int i = 0; i < M; i++) {
const int l = i * B;
const int r = std::min(1U * n, l + B);
u64 s = 0;
for (int j = l; j < r; j++) {
while (s && cmp(v[j], v[std::__lg(s) + l])) {
s ^= 1ULL << std::__lg(s);
}
s |= 1ULL << (j - l);
stk[j] = s;
}
}
}
T get(int l, int r) { // 0-index [l,r)
if (l / B != (r - 1) / B) {
T ans = std::min(suf[l], pre[r - 1], cmp);
l = l / B + 1;
r = r / B;
if (l < r) {
int k = std::__lg(r - l);
ans = std::min({ans, a[k][l], a[k][r - (1 << k)]}, cmp);
}
return ans;
} else {
int x = B * (l / B);
return ini[__builtin_ctzll(stk[r - 1] >> (l - x)) + l];
}
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, m;
std::cin >> n >> m;
std::vector<int> a(n);
for (int i = 0; i < n; i++) {
std::cin >> a[i];
}
RMQ<int, std::greater<int>> rmq(a);
while (m--) {
int l, r;
std::cin >> l >> r;
std::cout << rmq.get(l - 1, r) << "\n";
}
}
#include <bits/stdc++.h>
struct ST {
std::vector<std::vector<int>> f, g, h;
ST(int n, std::vector<int>& a) : f(n, std::vector<int>(std::__lg(n) + 1)), g(n, std::vector<int>(std::__lg(n) + 1)), h(n, std::vector<int>(std::__lg(n) + 1)) {
for (int i = 1; i < n; i++) {
f[i][0] = g[i][0] = h[i][0] = a[i];
}
for (int j = 1; (1LL << j) <= n; j++) {
for (int i = 0; i + (1LL << j) - 1 < n; i++) {
f[i][j] = std::max(f[i][j - 1], f[i + (1LL << (j - 1))][j - 1]);
g[i][j] = std::min(g[i][j - 1], g[i + (1LL << (j - 1))][j - 1]);
h[i][j] = std::__gcd(h[i][j - 1], h[i + (1LL << (j - 1))][j - 1]);
}
}
}
int askMax(int l, int r) { // 1-index [l,r]
int k = std::__lg(r - l + 1);
return std::max(f[l][k], f[r - (1LL << k) + 1][k]);
}
int askMin(int l, int r) {
int k = std::__lg(r - l + 1);
return std::min(g[l][k], g[r - (1LL << k) + 1][k]);
}
int askGcd(int l, int r) {
int k = std::__lg(r - l + 1);
return std::__gcd(h[l][k], h[r - (1LL << k) + 1][k]);
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, m;
std::cin >> n >> m;
std::vector<int> a(n + 1);
for (int i = 1; i <= n; i++) {
std::cin >> a[i];
}
ST st(n + 1, a);
while (m--) {
int l, r;
std::cin >> l >> r;
std::cout << st.askMax(l, r) << "\n";
}
}
字符串
字符串哈希
#include <bits/stdc++.h>
using u64 = unsigned long long;
const int MOD1 = 1e9 + 7, MOD2 = 1e9 + 9;
class StringHash { // 1-index [l, r]
public:
int P1, P2;
std::vector<u64> h1, h2, p1, p2;
StringHash() {
// std::mt19937 rng(std::chrono::steady_clock::now().time_since_epoch().count());
// P1 = std::uniform_int_distribution<int>(128, 10000)(rng);
// P2 = std::uniform_int_distribution<int>(128, 10000)(rng);
P1 = 131;
P2 = 13331;
}
template <typename Sequence>
void build(const Sequence& seq) {
int n = seq.size();
h1.resize(n + 1, 0);
h2.resize(n + 1, 0);
p1.resize(n + 1, 1);
p2.resize(n + 1, 1);
for (int i = 1; i <= n; i++) {
h1[i] = (h1[i - 1] * P1 + seq[i - 1]) % MOD1;
h2[i] = (h2[i - 1] * P2 + seq[i - 1]) % MOD2;
p1[i] = (p1[i - 1] * P1) % MOD1;
p2[i] = (p2[i - 1] * P2) % MOD2;
}
}
std::pair<u64, u64> get(int l, int r) {
u64 hash1 = (h1[r] - h1[l - 1] * p1[r - l + 1] % MOD1 + MOD1) % MOD1;
u64 hash2 = (h2[r] - h2[l - 1] * p2[r - l + 1] % MOD2 + MOD2) % MOD2;
return {hash1, hash2};
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
std::cin >> n;
std::vector<int> a(n), b;
for (int i = 0; i < n; i++) {
std::cin >> a[i];
}
b = a;
std::reverse(b.begin(), b.end());
StringHash sh1, sh2;
sh1.build(a);
sh2.build(b);
int tt;
std::cin >> tt;
while (tt--) {
int l, r;
std::cin >> l >> r;
if (sh1.get(l, r) == sh2.get(n - r + 1, n - l + 1)) {
std::cout << "YES\n";
} else {
std::cout << "NO\n";
}
}
}
字典树
普通Trie
#include <bits/stdc++.h>
using i64 = long long;
class Trie {
public:
int cnt;
std::vector<std::vector<int>> nex;
std::vector<int> f, pre;
Trie(int n) : cnt(0), nex(n, std::vector<int>(26)), f(n), pre(n) {
}
void insert(std::string s) {
int fa = 0;
pre[fa]++;
for (auto ch : s) {
int son = ch - 'a';
if (!nex[fa][son]) {
nex[fa][son] = ++cnt;
}
fa = nex[fa][son];
pre[fa]++;
}
f[fa] = 1;
}
bool find(const std::string &s) {
int fa = 0;
for (auto ch : s) {
int son = ch - 'a';
if (!nex[fa][son]) {
return false;
}
fa = nex[fa][son];
}
return f[fa];
}
int precnt(std::string s) {
int fa = 0;
for (auto ch : s) {
int son = ch - 'a';
if (!nex[fa][son]) {
return 0;
}
fa = nex[fa][son];
}
return pre[fa];
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
Trie trie(100);
trie.insert("abc");
trie.insert("abc");
trie.insert("abcd");
std::cout << trie.find("abc") << "\n";
std::cout << trie.precnt("a") << "\n";
}
01Trie
例题1
n个数字选2个使得异或值最大。
#include <bits/stdc++.h>
using i64 = long long;
const int N = 2e5 + 10;
struct Trie {
struct Node {
int son[2];
Node() {
son[0] = son[1] = 0;
}
};
std::vector<Node> trie;
int idx;
Trie(int n) : trie(n * 32), idx(0) {}
void insert(int x) {
int o = 0;
for (int i = 30; i >= 0; i--) {
int y = x >> i & 1;
if (!trie[o].son[y]) {
trie[o].son[y] = ++idx;
}
o = trie[o].son[y];
}
}
int maxxor(int x) {
int o = 0, res = 0;
for (int i = 30; i >= 0; i--) {
int y = x >> i & 1;
if (trie[o].son[!y]) {
o = trie[o].son[!y];
res |= (1 << i);
} else {
o = trie[o].son[y];
}
}
return res;
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
std::cin >> n;
std::vector<int> a(n + 1);
for (int i = 1; i <= n; i++) {
std::cin >> a[i];
}
Trie trie(n + 1);
trie.insert(a[1]);
int ans = 0;
for (int i = 2; i <= n; i++) {
ans = std::max(ans, trie.maxxor(a[i]));
trie.insert(a[i]);
}
std::cout << ans << "\n";
}
KMP
快速匹配s2在s1中出现的位置。
例题1
#include <bits/stdc++.h>
using i64 = long long;
template <typename T>
std::vector<int> calc_pi(const T& s2) {
int n = s2.size();
std::vector<int> pi(n);
for (int i = 1; i < n; i++) {
int j = pi[i - 1];
while (j > 0 && s2[i] != s2[j]) {
j = pi[j - 1];
}
if (s2[i] == s2[j]) {
j++;
}
pi[i] = j;
}
return pi;
}
template <typename T>
std::vector<int> kmp(const T& s1, const T& s2) {
std::vector<int> pos;
if (s2.empty() || s1.size() < s2.size()) {
return pos;
}
std::vector<int> pi = calc_pi(s2);
for (int i = 0, j = 0; i < s1.size(); i++) {
while (j > 0 && s1[i] != s2[j]) {
j = pi[j - 1];
}
if (s1[i] == s2[j]) {
j++;
}
if (j == s2.size()) {
pos.push_back(i - j + 2);
j = pi[j - 1];
}
}
return pos;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, m;
std::cin >> n >> m;
std::vector<int> s1(n), s2(m);
for (int i = 0; i < n; i++) {
std::cin >> s1[i];
}
for (int i = 0; i < m; i++) {
std::cin >> s2[i];
}
std::vector<int> pos = kmp(s1, s2), pi = calc_pi(s2);
for (auto idx : pos) {
std::cout << idx << "\n";
}
for (auto x : pi) {
std::cout << x << " ";
}
}
Manacher
处理回文相关问题。
#include <bits/stdc++.h>
template <typename T>
class Palindrome {
public:
std::vector<T> init(const std::vector<T>& s) {
std::vector<T> res;
res.push_back('#');
for (auto ch : s) {
res.push_back(ch);
res.push_back('#');
}
return res;
}
std::vector<int> manacher(const std::vector<T>& s) {
int n = s.size(), mid = 0, r = 0;
std::vector<int> R(n);
for (int i = 0; i < n; i++) {
if (i < r) {
R[i] = std::min(r - i, R[2 * mid - i]);
}
while (i + R[i] + 1 < n && i - R[i] - 1 >= 0 && s[i + R[i] + 1] == s[i - R[i] - 1]) {
R[i]++;
}
if (i + R[i] > r) {
mid = i;
r = i + R[i];
}
}
return R; // 以i为中心的最大回文半径
}
std::vector<T> maxpali(const std::vector<T>& s) { // 最长回文
auto res = init(s);
std::vector<int> R = manacher(res);
int maxn = 0, idx = 0;
for (int i = 0; i < R.size(); i++) {
if (R[i] > maxn) {
maxn = R[i];
idx = i;
}
}
return std::vector<T>(s.begin() + (idx - maxn) / 2, s.begin() + (idx - maxn) / 2 + maxn);
}
int cntpali(const std::vector<T>& s) { // 回文数量
auto res = init(s);
std::vector<int> R = manacher(res);
int cnt = 0;
for (auto r : R) {
cnt += (r + 1) / 2;
}
return cnt;
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
Palindrome<char> pali1;
Palindrome<int> pali2;
std::string s;
std::cin >> s;
std::vector<char> v(s.begin(), s.end());
std::cout << pali1.cntpali(v) << "\n";
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; i++) {
std::cin >> a[i];
}
std::cout << pali2.maxpali(a).size() << "\n";
}
最小表示法
字符串s的最小表示为与s循环同构的所有字符串中字典序最小的字符串。
例题1
求一个序列的最小表示。
#include <bits/stdc++.h>
template <typename T>
int minshow(const std::vector<T>& a, int n) {
int i = 0, j = 1, k = 0;
while (i < n && j < n && k < n) {
if (a[(i + k) % n] == a[(j + k) % n]) {
k++;
} else {
if (a[(i + k) % n] > a[(j + k) % n]) {
i += k + 1;
} else {
j += k + 1;
}
if (i == j) {
i++;
}
k = 0;
}
}
return std::min(i, j);
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; i++) {
std::cin >> a[i];
}
int cur = minshow(a, n);
for (int i = 0; i < n; i++) {
std::cout << a[(i + cur) % n] << " \n"[i == n - 1];
}
}
计算几何
凸包
#include <bits/stdc++.h>
template <typename T>
struct Point {
T x, y;
bool operator<(const Point &p) const {
return x < p.x || (x == p.x && y < p.y);
}
bool operator==(const Point &p) const {
return x == p.x && y == p.y;
}
};
template <typename T>
T dist(Point<T> a, Point<T> b) { // 两点距离
return std::sqrt((b.x - a.x) * (b.x - a.x) + (b.y - a.y) * (b.y - a.y));
}
template <typename T>
T cross(Point<T> a, Point<T> b, Point<T> c) { // 向量叉积
return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
}
template <typename T>
T perimeter(std::vector<Point<T>> poly) { // 凸包周长
T res = 0;
int n = poly.size();
for (int i = 0; i < n; i++) {
res += dist(poly[i], poly[(i + 1) % n]);
}
return res;
}
template <typename T>
T area(const std::vector<Point<T>> &hull, int k) { // 凸包内接最大k边形面积
int n = hull.size();
if (k > n || k < 3) {
return -1;
}
// 凸包面积
if (k == n) {
T Area = 0;
for (int i = 0; i < n; i++) {
int j = (i + 1) % n;
Area += hull[i].x * hull[j].y - hull[j].x * hull[i].y;
}
return std::abs(Area) / 2.0;
}
// 最大三角形面积
if (k == 3) {
T Area = 0;
std::vector<Point<T>> temp_hull = hull;
temp_hull.push_back(hull[0]);
for (int i = 0; i < n; i++) {
int j = (i + 1) % n;
int k = (j + 1) % n;
while (j != k && k != i) {
while (cross(temp_hull[i], temp_hull[j], temp_hull[k + 1]) > cross(temp_hull[i], temp_hull[j], temp_hull[k])) {
k = (k + 1) % n;
}
double now = std::abs(cross(temp_hull[i], temp_hull[j], temp_hull[k])) / 2.0;
Area = std::max(Area, now);
j = (j + 1) % n;
}
}
return Area;
}
// 最大四边形面积
if (k == 4) {
T Area = 0;
for (int i = 0; i < n; i++) {
int a = i, b = (i + 1) % n;
for (int j = i + 1; j < n; j++) {
while (cross(hull[i], hull[j], hull[(a + 1) % n]) < cross(hull[i], hull[j], hull[a])) {
a = (a + 1) % n;
}
while (cross(hull[i], hull[j], hull[(b + 1) % n]) > cross(hull[i], hull[j], hull[b])) {
b = (b + 1) % n;
}
Area = std::max(Area, -cross(hull[i], hull[j], hull[a]) + cross(hull[i], hull[j], hull[b]));
}
}
return Area / 2.0;
}
// 初始化所有可能的三角形
std::vector<std::vector<double>> dp(n, std::vector<double>(n, 0.0));
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
for (int m = j + 1; m < n; m++) {
double now = std::abs(cross(hull[i], hull[j], hull[m])) / 2.0;
dp[j][m] = std::max(dp[j][m], now);
}
}
}
for (int l = 4; l <= k; l++) {
std::vector<std::vector<double>> ndp(n, std::vector<double>(n, 0.0));
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
for (int m = j + 1; m < n; m++) {
double now = dp[i][j] + std::abs(cross(hull[i], hull[j], hull[m])) / 2.0;
ndp[j][m] = std::max(ndp[j][m], now);
}
}
}
dp = ndp;
}
T Area = 0;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
Area = std::max(Area, dp[i][j]);
}
}
return Area;
}
template <typename T>
std::vector<Point<T>> andrew(std::vector<Point<T>> points) { // Andrew求凸包
std::sort(points.begin(), points.end());
std::vector<Point<T>> hull;
for (int i = 0; i < 2; i++) {
int st = hull.size(), siz = hull.size();
for (auto p : points) {
while (siz >= st + 2 && (hull[siz - 1].x - hull[siz - 2].x) * (p.y - hull[siz - 2].y) <= (hull[siz - 1].y - hull[siz - 2].y) * (p.x - hull[siz - 2].x)) {
hull.pop_back();
siz--;
}
hull.push_back(p);
siz++;
}
hull.pop_back();
std::reverse(points.begin(), points.end());
}
if (hull.size() == 2 && hull[0] == hull[1]) {
hull.pop_back();
}
return hull;
}
template <typename T>
T diameter(std::vector<Point<T>> hull) { // 求凸包直径
int n = hull.size();
T res = 0;
for (int i = 0, j = 1; i < n; i++) {
while (cross(hull[i], hull[(i + 1) % n], hull[j]) < cross(hull[i], hull[(i + 1) % n], hull[(j + 1) % n])) {
j = (j + 1) % n;
}
res = std::max(res, std::max(dist(hull[i], hull[j]), dist(hull[(i + 1) % n], hull[j])));
}
return res;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n;
std::cin >> n;
std::vector<Point<double>> points(n);
for (int i = 0; i < n; i++) {
std::cin >> points[i].x >> points[i].y;
}
std::vector<Point<double>> hull = andrew(points);
for (auto [x, y] : hull) {
std::cout << x << " " << y << "\n";
}
std::cout << "\n";
std::cout << std::fixed << std::setprecision(2) << perimeter(hull) << "\n\n";
std::cout << std::fixed << std::setprecision(2) << diameter(hull) << "\n\n";
for (int i = 3; i <= hull.size(); i++) {
std::cout << i << " " << area(hull, i) << "\n";
}
std::cout << "\n";
}
pick定理
给定顶点均为整点的简单多边形,其面积 $ A $ 和内部格点数目 $ i $、边上格点数目 $ b $ 的关系:$ 2A = 2i + b - 2 $。
例题1
在直角坐标系中,给定一个机器人从原点出发进行多次移动,每次移动由一对整数(dx, dy)定义,求出由这些移动形成的封闭多边形的边上的点的数量、多边形内的点的数量以及多边形的面积。
#include <bits/stdc++.h>
struct Point {
int x, y;
bool operator<(const Point &p) const {
return x < p.x || (x == p.x && y < p.y);
}
};
double area(std::vector<Point> poly) {
double res = 0.0;
int n = poly.size();
for (int i = 0; i < n; i++) {
int j = (i + 1) % n;
res += poly[i].x * poly[j].y - poly[j].x * poly[i].y;
}
return std::abs(res) / 2.0;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int tt;
std::cin >> tt;
for (int _ = 1; _ <= tt; _++) {
int n, on = 0, in = 0;
std::cin >> n;
std::vector<Point> poly = {{0, 0}};
for (int i = 1; i <= n; i++) {
int x, y;
std::cin >> x >> y;
poly.push_back({poly[i - 1].x + x, poly[i - 1].y + y});
on += std::gcd(std::abs(x), std::abs(y));
}
double S = area(poly);
in = (2 * S + 2 - on) / 2;
std::cout << "Scenario #" << _ << ":\n";
std::cout << in << " " << on << " " << S << "\n";
}
}
杂项
莫队
普通莫队
离线询问后排序,顺序处理每个询问,暴力从上一个区间的答案转移到下一个区间(一步一步移动即可)。
例题1
给定一个序列a和q次区间询问,回答区间有多少个不一样的数字。
#include <bits/stdc++.h>
const int N = 1e6 + 10;
inline int read() {
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') {
f = -1;
}
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + ch - '0';
ch = getchar();
}
return x * f;
}
inline void write(int x) {
if (x < 0) {
putchar('-'), x = -x;
}
if (x > 9) {
write(x / 10);
}
putchar(x % 10 + '0');
return;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n = read();
int size = std::sqrt(n);
int bnum = ceil((double)n / size);
std::vector<int> a(n + 1), cnt(N), block(n + 1);
for (int i = 1; i <= bnum; i++) {
for (int j = (i - 1) * size + 1; j <= i * size && j <= n; j++) {
block[j] = i;
}
}
for (int i = 1; i <= n; i++) {
a[i] = read();
}
int q = read();
std::vector<std::array<int, 3>> ask(q);
for (int i = 0; i < q; i++) {
ask[i][0] = read();
ask[i][1] = read();
ask[i][2] = i;
}
std::sort(ask.begin(), ask.end(), [&](std::array<int, 3> a, std::array<int, 3> b) {
if (block[a[0]] != block[b[0]]) {
return block[a[0]] < block[b[0]];
}
if (block[a[0]] & 1) {
return a[1] < b[1];
} else {
return a[1] > b[1];
}
});
int l = 1, r = 0, now = 0;
std::vector<int> ans(q);
for (auto [L, R, idx] : ask) {
while (l > L) {
l--;
now += !cnt[a[l]];
cnt[a[l]]++;
}
while (r < R) {
r++;
now += !cnt[a[r]];
cnt[a[r]]++;
}
while (l < L) {
cnt[a[l]]--;
now -= !cnt[a[l]];
l++;
}
while (r > R) {
cnt[a[r]]--;
now -= !cnt[a[r]];
r--;
}
ans[idx] = now;
}
for (int i = 0; i < q; i++) {
write(ans[i]);
putchar('\n');
}
}
其他
指令集优化
#pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4.1,sse4.2,avx,avx2,popcnt,tune=native")
I/O操作
// int128
#include <bits/stdc++.h>
using i128 = __int128;
std::ostream &operator<<(std::ostream &out, i128 val) {
if (val == 0) {
return out << "0";
}
if (val < 0) {
out << '-', val = -val;
}
std::string s;
while (val > 0) {
s += '0' + val % 10;
val /= 10;
}
std::reverse(s.begin(), s.end());
return out << s;
}
std::istream &operator>>(std::istream &in, i128 &val) {
std::string s;
in >> s;
val = 0;
bool neg = (s[0] == '-');
for (int i = neg; i < s.size(); i++) {
val = val * 10 + (s[i] - '0');
}
if (neg) {
val = -val;
}
return in;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
i128 n;
std::cin >> n;
std::cout << n <<"\n";
}
// 快读快写
#include <bits/stdc++.h>
inline int read() {
int x = 0, f = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-') {
f = -1;
}
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + ch - '0';
ch = getchar();
}
return x * f;
}
inline void write(int x) {
if (x < 0) {
putchar('-'), x = -x;
}
if (x > 9) {
write(x / 10);
}
putchar(x % 10 + '0');
return;
}
int main() {
int n = read();
write(n);
}
PBDS
例题1
让multiset实现lowerbound和upperbound。
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
typedef __gnu_pbds::tree<
std::pair<int, int>,
__gnu_pbds::null_type,
std::less<std::pair<int, int>>,
__gnu_pbds::rb_tree_tag,
__gnu_pbds::tree_order_statistics_node_update
> ordered_multiset;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int n, order = 0;
std::cin >> n;
ordered_multiset omst;
for (int i = 0; i < n; i++) {
int x;
std::cin >> x;
omst.insert({x, order++});
}
int k;
std::cin >> k;
std::cout << omst.order_of_key({k, 0}) << " "; // lower_bound
std::cout << omst.order_of_key({k, std::numeric_limits<int>::max()}); // upper_bound
}
对拍
:loop
python data.py > data.in
python right.py < data.in > right.out
::right.exe< data.in > err.out
err.exe < data.in > err.out
fc err.out right.out
if %errorlevel%==0 goto loop
pause
__builtin_系列函数
__builtin_popcount(x) 统计x的二进制表达中有多少个1
__builtin_clz(x) 统计x的二进制表达中有多少个前导0
__builtin_ctz(x) 统计x的二进制表达中末尾有多少个0
__builtin_ffs(x) 统计x的二进制表达中最后一位1是从后往前第几位
__builtin_parity(x) 判断x的二进制表达中1个数的奇偶性
Python
sys
import sys
sys.set_int_max_str_digits(2 ** 31 - 1) # 最大位数
sys.setrecursionlimit(2 ** 31 - 1) # 最大递归深度
input = sys.stdin.readline # 流读入
itertools
import itertools
perms = list(itertools.permutations([1, 2, 3], 2)) # 排列
combs = list(itertools.combinations([1, 2, 3], 2)) # 组合
combs_wr = list(itertools.combinations_with_replacement([1, 2, 3], 2)) # 允许元素反复被选的组合
prod = list(itertools.product([1, 2], [3, 4], [5, 6])) # 笛卡尔积
functools
from functools import cache
@cache # cache装饰器起到记忆化搜索的作用
def fibo(x):
if x < 3:
return 1
else:
return fibo(x - 1) + fibo(x - 2)
decimal
from decimal import *
getcontext().prec = 1001 # 设置全局精度为1001位小数
a, b, k = map(Decimal, input().split())
print((a / b).quantize(Decimal("0." + ("0" * int(k - 1)) + "1"), rounding = ROUND_HALF_UP)) # 保留k位四舍五入
fractions
from fractions import Fraction
f = Fraction(14, 22)
print(f) # 会自动化成最简
print(f.numerator) # 分子
print(f.denominator) # 分母
print(f.limit_denominator(5)) # 限制分母不超过5,最接近14/22的一个分数
datetime
from datetime import datetime, timedelta
time1 = datetime(2024, 11, 9, 2, 3, 4)
time2 = datetime(2023, 10, 8, 1, 2, 3)
print(time1.weekday()) # time1星期几(0 = Monday)
print((time1 - time2).total_seconds()) # 差多少秒
print((time1 - time2).days) # 差多少天
print(time1.strftime('%Y-%m-%d %H:%M:%S'))
print(time1 + timedelta(days=10))
print(time1 - timedelta(hours=1))
例题1
求1000年到9999年中2月29日是疯狂星期四的年份。
from datetime import datetime
ans = []
for year in range(1000, 9999 + 1):
try:
date = datetime(year, 2, 29)
if date.weekday() == 3:
ans.append(year)
except:
pass
print(ans)

浙公网安备 33010602011771号