SCCPC 2025 四川省赛

I. Essentially Different Suffixes

题目大意

给你n个字符串,问所有字符串的所有后缀有多少种

解题思路

字符串哈希或tire存后缀,直接set或map塞string会爆空间

代码实现

#include <bits/stdc++.h>

using i64 = long long;

using u64 = unsigned long long;
const int MOD1 = 1e9 + 7, MOD2 = 1e9 + 9;

class StringHash {
   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::set<std::pair<u64, u64>> st;
    for (int i = 0; i < n; i++) {
        std::string s;
        std::cin >> s;
        std::reverse(s.begin(), s.end());
        StringHash sh;
        sh.build(s);
        for (int j = 1; j <= s.size(); j++) {
            st.insert(sh.get(1, j));
        }
    }

    std::cout << st.size() << "\n";
}

F. Inversion Pairs

题目大意

给定一个01串,部分位置由?填充,这些位置可以填0也可以填1,问最多能让字符串有多少个逆序对

解题思路

填充必然是一段1然后一段0,枚举01分界取max即可

代码实现

#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;
        std::cin >> n;

        std::string s;
        std::cin >> s;
        s = " " + s;

        std::vector<i64> pre0(n + 1), pre1(n + 1);
        for (int i = 1; i <= n; i++) {
            pre0[i] = pre0[i - 1] + (s[i] == '0');
            pre1[i] = pre1[i - 1] + (s[i] != '0');
        }
        std::vector<int> suff0(n + 2), suff1(n + 2);
        for (int i = n; i >= 1; i--) {
            suff0[i] = suff0[i + 1] + (s[i] != '1');
            suff1[i] = suff1[i + 1] + (s[i] == '1');
        }

        i64 ans = 0, res = 0;
        for (int i = n; i >= 1; i--) {
            if (s[i] == '1') {
                ans += suff0[i + 1];
            }
        }
        res = ans;

        for (int i = 1; i <= n; i++) {
            if (s[i] == '?') {
                res -= pre1[i - 1];
                res += suff0[i + 1];
                ans = std::max(ans, res);
            }
        }

        std::cout << ans << "\n";
    }
}

J. Sichuan Provincial Contest

题目大意

给定一棵点权为字母的树,问有多少条链是“SCCPC”

解题思路

枚举中间点C,然后乘上“SC”和“PC”的数量,注意对“PC”去重

代码实现

#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;
        std::cin >> n;

        std::string s;
        std::cin >> s;
        s = " " + s;

        auto check1 = [&](int u, int v) -> bool {
            std::set<std::string> st = {"SC", "CC", "CP", "PC"};
            std::string t;
            t = s[u];
            t += s[v];
            return st.count(t);
        };
        std::vector<int> cntl(n + 1), cntr(n + 1);
        auto check2 = [&](int u, int v) -> void {
            if (s[u] == 'S' && s[v] == 'C') {
                cntl[v]++;
            }
            if (s[u] == 'C' && s[v] == 'S') {
                cntl[u]++;
            }
            if (s[u] == 'C' && s[v] == 'P') {
                cntr[v]++;
            }
            if (s[u] == 'P' && s[v] == 'C') {
                cntr[u]++;
            }
        };

        std::vector<std::vector<int>> g(n + 1, std::vector<int>());
        for (int i = 1; i < n; i++) {
            int u, v;
            std::cin >> u >> v;
            if (check1(u, v)) {
                g[u].emplace_back(v);
            }
            if (check1(v, u)) {
                g[v].emplace_back(u);
            }
            check2(u, v);
        }

        i64 ans = 0;
        for (int u = 1; u <= n; u++) {
            if (s[u] != 'C') {
                continue;
            }
            i64 ans1 = 0, ans2 = 0;
            for (auto v : g[u]) {
                if (s[v] == 'C') {
                    ans1 += cntl[v];
                }
                if (s[v] == 'P') {
                    ans2 += cntr[v] - 1;
                }
            }
            ans += ans1 * ans2;
        }

        std::cout << ans << "\n";
    }
}

K. Point Divide and Conquer

题目大意

给定排列表示点分治每次分治的中心,问最后点分树上每个点的父亲

解题思路

题意为每次删除的割点后其他连通块的父亲都是这个点,正着求较为困难,考虑倒着做,每次枚举一个新点p将其加入图中,合并原来的连通块并将p作为连通块的根,可以用并查集来维护

代码实现

#include <bits/stdc++.h>

using i64 = long long;

class DSU {
   public:
    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 x) {
        if (fa[x] != x) {
            fa[x] = find(fa[x]);
        }
        return fa[x];
    }

    void merge(int x, int y) {
        int X = find(x), Y = find(y);
        if (X != Y) {
            fa[Y] = X;
            siz[X] += siz[Y];
            cnt--;
        }
    }

    int size() {
        return cnt;
    }

    int count(int x) {
        return siz[find(x)];
    }
};

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;
        std::cin >> n;

        std::vector<int> p(n + 1), pos(n + 1), ans(n + 1), f(n + 1);
        std::vector<std::vector<int>> g(n + 1, std::vector<int>());

        for (int i = 1; i <= n; i++) {
            std::cin >> p[i];
            pos[p[i]] = i;
        }

        for (int i = 0; i < n - 1; i++) {
            int u, v;
            std::cin >> u >> v;
            g[u].push_back(v);
            g[v].push_back(u);
        }

        DSU dsu(n);
        for (int i = n; i >= 1; i--) {
            int u = p[i];
            f[u] = 1;
            for (auto v : g[u]) {
                if (f[v]) {
                    int fa = dsu.find(v);
                    if (fa != u) {
                        ans[fa] = u;
                        dsu.merge(u, fa);
                    }
                }
            }
        }

        for (int i = 1; i <= n; i++) {
            std::cout << ans[i] << " \n"[i == n];
        }
    }
}

H. Hututu

题目大意

从点(x,y)可以走到(x±1,y±1),(x±1,y±2),(x±2,y±1),(x±2y±2),问走到(X,Y)的最小步数

解题思路

根据xy与XY差值分类讨论即可

代码实现

#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--) {
        i64 x, y, X, Y;
        std::cin >> x >> y >> X >> Y;

        if (x == X) {
            if (std::abs(y - Y) == 1 || std::abs(y - Y) == 2) {
                std::cout << 2 << "\n";
            } else {
                std::cout << (std::abs(y - Y) + 1) / 2 << "\n";
            }
        } else if (y == Y) {
            if (std::abs(x - X) == 1 || std::abs(x - X) == 2) {
                std::cout << 2 << "\n";
            } else {
                std::cout << (std::abs(x - X) + 1) / 2 << "\n";
            }
        } else {
            std::cout << (std::max(std::abs(x - X), std::abs(y - Y)) + 1) / 2 << "\n";
        }
    }
}

A. Minimum Product

题目大意

n点m边的有向图,每条边有ab两个属性,设从节点1到节点n的路径为P,最小化\(\sum_{i \in P} a_i \times \sum_{i \in P} b_i\)

解题思路

考虑设计dp状态dp[suma][u]为到达u节点且\(\sum a_i\)为suma时\(\sum b_i\)的最小值,直接枚举suma和可行边暴力转移即可

代码实现

#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, m;
        std::cin >> n >> m;

        std::vector<std::array<i64, 4>> edge(m);
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < 4; j++) {
                std::cin >> edge[i][j];
            }
        }

        std::vector<std::vector<i64>> dp(n + 1, std::vector<i64>(60001, 1e12));
        dp[1][0] = 0;
        for (int i = 0; i <= 60000; i++) {
            for (int j = 0; j < m; j++) {
                auto [u, v, a, b] = edge[j];
                if (i + a <= 60000) {
                    dp[v][i + a] = std::min(dp[v][i + a], dp[u][i] + b);
                }
            }
        }

        i64 suma = 1e9, sumb = 1e9;
        for (int i = 0; i <= 60000; i++) {
            if (dp[n][i] != 1e12) {
                if (i * dp[n][i] < suma * sumb) {
                    suma = i;
                    sumb = dp[n][i];
                }
            }
        }

        std::cout << suma << " " << sumb << "\n";
    }
}

C. Optimal Time

题目大意

定义S(x)是x的所有约数与x的所有不超过N的倍数的并集

设当前状态为x,每一秒可以有两种选择

  • 什么都不做
  • 等概率改为S(x)中的数字

一秒后x -= 1,问最优情况下x到0的期望时间

解题思路

设dp[x]是x的答案,根据题目可以得到转移方程\(dp[i]=\min(dp[i-1],\frac{\sum_{j\in S(i)}dp[j-1]}{|S(i)|})\),由于这里的dp是有后效性的,因此需要考虑用迭代的方式让其不断逼近准确值,约100次即可(证明

代码实现

#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, q;
    std::cin >> n >> q;

    std::vector<double> dp(n + 1);
    std::vector<std::vector<int>> fac(n + 1, std::vector<int>());
    for (int i = 1; i <= n; i++) {
        dp[i] = i;
        for (int j = i; j <= n; j += i) {
            fac[j].push_back(i);
        }
    }

    for (int i = 0; i < 100; i++) {
        for (int j = 1; j <= n; j++) {
            double sum = 0, siz = 0;
            for (auto x : fac[j]) {
                sum += dp[x - 1];
                siz++;
            }
            for (int k = 2; j * k <= n; k++) {
                sum += dp[j * k - 1];
                siz++;
            }

            dp[j] = std::min(dp[j - 1], sum / siz) + 1;
        }
    }

    while (q--) {
        int x;
        std::cin >> x;
        std::cout << std::fixed << std::setprecision(6) << dp[x] << "\n";
    }
}
posted @ 2025-07-13 02:31  udiandianis  阅读(352)  评论(0)    收藏  举报