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

给定背包容量mn件物品,每件物品有重量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

给定背包容量mn件物品,每件物品有重量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

给定背包容量mn组物品,每组物品有数量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

给定背包容量mn件物品,每件物品有重量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

给定长度分别为nm的两个字符串s1s2,计算它们的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\) 的方程组的最小非负整数解(扩展的区别在于模数是不是互质)。

\[\begin{cases} x \equiv b_1 \ (\text{mod} \ a_1) \\ x \equiv b_2 \ (\text{mod} \ a_2) \\ \ldots \\ x \equiv b_n \ (\text{mod} \ a_n) \end{cases} \]

// 扩展中国剩余定理
#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

\[\sum_{i=x}^{n} \sum_{j=y}^{m} [\gcd(i, j) = k] \]

#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

\[\sum_{i=1}^{n} \sum_{j=1}^{m} \text{lcm}(i, j) \]

#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

\[\sum_{i=1}^{n} \sum_{j=1}^{m} d(i \cdot j) \]

#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

一堆物品,每次可以取 1m 个,取到最后一个物品的人获胜,问先手能否必胜。

  • 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函数

博弈的步骤可以变成一个nm边的有向无环图,问先手能否必胜。

  • 根据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

\[\begin{cases} a_{1,1}x_1 + a_{1,2}x_2 + \cdots + a_{1,n}x_n = b_1 \\ a_{2,1}x_1 + a_{2,2}x_2 + \cdots + a_{2,n}x_n = b_2 \\ \cdots \\ a_{n,1}x_1 + a_{n,2}x_2 + \cdots + a_{n,n}x_n = b_n \\ \end{cases} \]

#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

快速匹配s2s1中出现的位置。

例题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

给定一个序列aq次区间询问,回答区间有多少个不一样的数字。

#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)
posted @ 2025-04-11 15:51  udiandianis  阅读(75)  评论(0)    收藏  举报