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";
}
}

浙公网安备 33010602011771号