VP The 2025 Sichuan Provincial Collegiate Programming Contest
A. Minimum Product
题意:给你一个无向图,每条边有\(a_i\)和\(b_i\)两个属性。求一条\(1\)到\(n\)的最短路,路径的权值为\((\sum a_u) \times (\sum b_u)\)。
正解应该是一个背包。我写了\(dijkstra\)加一个剪枝跑过去了。
记\(dist[u][t]\)表示到\(u\)且\(a\)的和为\(t\)时的最小和,不过其实记最小\(b\)的和就行了,我当时发现一个这个式子加一个\(a, b\)也能算就没想那么多。然后就是最短路了,加了一个超过答案就\(continue\)的剪枝,跑了\(218 ms\)。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n, m;
std::cin >> n >> m;
std::vector<std::vector<std::tuple<int, int, int>>> adj(n);
int k = 0;
for (int i = 0; i < m; ++ i) {
int u, v, a, b;
std::cin >> u >> v >> a >> b;
-- u, -- v;
adj[u].emplace_back(v, a, b);
k += a;
}
k = std::min(k, 200 * (n - 1));
const i64 inf = 1e18;
std::vector dist(n, std::vector<i64>(k + 1, inf));
using A = std::tuple<i64, int, int>;
std::priority_queue<A, std::vector<A>, std::greater<A>> heap;
heap.emplace(0, 0, 0);
dist[0][0] = 0;
i64 min = inf, a, b;
while (heap.size()) {
auto [d, u, t] = heap.top(); heap.pop();
if (d != dist[u][t]) {
continue;
}
if (d > min) {
continue;
}
if (u == n - 1) {
if (d < min || (d == min && t < a)) {
min = d;
a = t;
b = d / t;
}
continue;
}
for (auto & [v, a, b] : adj[u]) {
i64 sumb = t ? dist[u][t] / t : 0;
if (t + a <= k && dist[v][t + a] > dist[u][t] + (i64)a * sumb + (i64)b * t + (i64)a * b) {
dist[v][t + a] = dist[u][t] + (i64)a * sumb + (i64)b * t + (i64)a * b;
heap.emplace(dist[v][t + a], v, t + a);
}
}
}
std::cout << a << " " << b << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
C. Optimal Time
题意:\(S(x)\)是\(x\)的所有约数与\(x\) 的所有不超过 \(N\) 的倍数的并集。假设当前状态是 \(x\),每秒可以决定等概率变成 \(S(x)\) 中的一个数,或者保持不变,每秒结束后 \(x ← x − 1\),给定初始状态 \(x\),需要求出最优决策下到 \(0\) 的期望时间。
赛时半天没想出来,没想到居然是模拟\(100\)次就行了。
考虑\(dp\),\(f[i][j]\)表示\(i\)迭代\(j\)轮后的期望,那么有\(f[i][j] = \min(f[i - 1][j - 1], \frac{\sum_{k\in S(i)}f[k - 1][j - 1]}{|S(i)|})+1\)。
按题解说法模拟\(100\)轮就收敛了,不是很懂。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n, q;
std::cin >> n >> q;
const int K = 100;
std::vector<double> f(n + 1);
std::ranges::iota(f, 0);
std::vector<int> cnt(n + 1, 1);
for (int i = 1; i <= n; ++ i) {
for (int j = i + i; j <= n; j += i) {
++ cnt[i];
++ cnt[j];
}
}
for (int t = 0; t < 100; ++ t) {
std::vector<double> sum(n + 1);
for (int i = 1; i <= n; ++ i) {
for (int j = i + i; j <= n; j += i) {
sum[j] += f[i - 1];
}
}
for (int i = 1; i <= n; ++ i) {
double tot = sum[i];
for (int j = i; j <= n; j += i) {
tot += f[j - 1];
}
f[i] = std::min(f[i - 1], tot / cnt[i]) + 1;
}
}
std::cout << std::fixed << std::setprecision(12);
while (q -- ) {
int x;
std::cin >> x;
std::cout << f[x] << "\n";
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
// std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
F. Inversion Pairs
题意:一个\(01\)串,有些地方需要你去填。你要使得逆序对最大。
猜测是左边放\(1\)右边放\(0\)。那么枚举分割点,先处理\(pre_i, suf_i\)分别表示一个前缀的问号都填\(1\)和后面产生的逆序对已经后缀都填\(0\)和前面产生的逆序对,然后记下前缀后缀的问号的个数,那么就可以多出来\(pre_i + suf_i + pre_{cnt?} \times suf_{cnt?}\)。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
std::cin >> n;
std::string s;
std::cin >> s;
std::vector<int> sum0(n + 1), sum1(n + 1);
for (int i = 0; i < n; ++ i) {
sum1[i + 1] = sum1[i] + (s[i] == '1');
sum0[i + 1] = sum0[i] + (s[i] == '0');
}
std::vector<i64> pre(n + 1), suf(n + 2);
for (int i = 0; i < n; ++ i) {
pre[i + 1] = pre[i];
if (s[i] == '?') {
pre[i + 1] += sum0[n] - sum0[i];
}
}
for (int i = n - 1; i >= 0; -- i) {
suf[i + 1] = suf[i + 2];
if (s[i] == '?') {
suf[i + 1] += sum1[i];
}
}
int tot = std::ranges::count(s, '?');
int cnt = 0;
i64 ans = std::max(suf[1], pre[n]);
for (int i = 1; i <= n; ++ i) {
cnt += s[i - 1] == '?';
ans = std::max(ans, pre[i] + suf[i + 1] + (i64)cnt * (tot - cnt));
}
for (int i = 0; i < n; ++ i) {
if (s[i] == '0') {
ans += sum1[i + 1];
}
}
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
H. Hututu
题意:从\((x, y)\)走到\((X, Y)\)。每次一个坐标可以移动\(1\)或者\(2\)个单位。求最少移动次数。
记\(dx, dy\)为横坐标纵坐标的距离,令\(dx < dy\),那么横坐标最少走\(\lceil \frac{dx}{2} \rceil\)步,最多走\(dx\)步,同理可以得到纵坐标最少最多走几步,如果它们有交集,那么就是走\(\lceil \frac{dy}{2} \rceil\)步就行了。否则如果\(\lceil \frac{dy}{2} \rceil - dx = 1\)需要多走一步,不然也是\(\lceil \frac{dy}{2} \rceil\)步。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
i64 x, y, X, Y;
std::cin >> x >> y >> X >> Y;
i64 dx = std::abs(x - X), dy = std::abs(y - Y);
if (dx > dy) {
std::swap(dx, dy);
}
if ((dx + 1) / 2 <= (dy + 1) / 2 && (dy + 1) / 2 <= dx) {
std::cout << (dy + 1) / 2 << "\n";
return;
}
i64 t = (dy + 1) / 2 - dx;
if (t == 1) {
std::cout << (dy + 1) / 2 + 1 << "\n";
} else {
std::cout << (dy + 1) / 2 << "\n";
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
I. Essentially Different Suffixes
题意:求不同后缀个数。
字典树板子。从后往前插每个字符串到字典树里,看字典树里有多少个不同的结点就有几个后缀。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
const int N = 3e5 + 5;
int tr[N][26], idx;
struct Trie {
Trie() {
idx = 0;
new_node();
}
int new_node() {
memset(tr[idx], 0, sizeof tr[idx]);
return idx ++ ;
}
void insert(const std::string & s) {
int p = 0;
for (auto & c : s) {
int x = c - 'a';
if (!tr[p][x]) {
tr[p][x] = new_node();
}
p = tr[p][x];
}
}
};
void solve() {
int n;
std::cin >> n;
std::vector<std::string> s(n);
for (int i = 0; i < n; ++ i) {
std::cin >> s[i];
std::ranges::reverse(s[i]);
}
Trie tr;
for (int i = 0; i < n; ++ i) {
tr.insert(s[i]);
}
std::cout << idx - 1 << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
// std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
J. Sichuan Provincial Contest
题意:一棵树每个点有一个字母,求有多少路径可以组成"SCCPC"。
考虑\(dp\)。\(f[i][j]\)表示到达\(i\)是正着写到了第\(j\)位,和\(g[i][j]\)表示到达\(i\)时倒着在第\(j\)位。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
std::cin >> n;
std::string s;
std::cin >> s;
std::vector<std::vector<int>> adj(n);
for (int i = 1; i < n; ++ i) {
int u, v;
std::cin >> u >> v;
-- u, -- v;
adj[u].push_back(v);
adj[v].push_back(u);
}
std::string t = "SCCPC";
std::vector f(n, std::array<i64, 6>{});
std::vector g(n, std::array<i64, 6>{});
i64 ans = 0;
auto dfs = [&](auto & self, int u, int fa) -> void {
if (s[u] == 'S') {
f[u][1] += 1;
}
if (s[u] == 'C') {
g[u][1] += 1;
}
for (auto & v : adj[u]) {
if (v == fa) {
continue;
}
self(self, v, u);
for (int i = 1; i < 5; ++ i) {
ans += [u][i] * g[v][5 - i];
ans += g[u][i] * f[v][5 - i];
}
for (int i = 0; i < 5; ++ i) {
if (s[u] == t[i]) {
f[u][i + 1] += f[v][i];
}
}
for (int i = 0; i < 5; ++ i) {
if (s[u] == t[4 - i]) {
g[u][i + 1] += g[v][i];
}
}
}
};
dfs(dfs, 0, -1);
std::cout << ans << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
K. Point Divide and Conquer
题意:每次选排列里出现最前的点把数分隔位多个联通块,然后每个联通块最前的点当它的子节点,并且对每个联通块递归操作。求每个点的父节点。
正解很简单。就是从后往前看,如果和当前点连边的点有在后面的且没有父节点的点,那这个点就是它的父节点。
我直接大力模拟。用树链剖分加线段树维护。写了两个小时过了。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
struct HLD {
std::vector<int> in, out, size, deep, fa, top, idx;
std::vector<std::vector<int>> adj;
int n, dfn;
HLD(){}
HLD(int _n) {
init(_n);
}
void init(int _n) {
n =_n;
in.resize(n);
out.resize(n);
size.resize(n);
deep.resize(n);
fa.resize(n);
top.resize(n);
idx.resize(n);
dfn = 0;
adj.assign(n, {});
}
void addEdge(int u, int v) {
adj[u].push_back(v);
adj[v].push_back(u);
}
void work(int root = 0) {
top[root] = root;
fa[root] = -1;
deep[root] = 0;
dfs1(root);
dfs2(root);
}
void dfs1(int u) {
if (fa[u] != -1) {
adj[u].erase(std::find(adj[u].begin(), adj[u].end(), fa[u]));
}
size[u] = 1;
for (int i = 0; i < adj[u].size(); ++ i) {
int v = adj[u][i];
fa[v] = u;
deep[v] = deep[u] + 1;
dfs1(v);
size[u] += size[v];
if (size[v] > size[adj[u][0]]) {
std::swap(adj[u][0], adj[u][i]);
}
}
}
void dfs2(int u) {
idx[dfn] = u;
in[u] = dfn ++ ;
for (auto & v : adj[u]) {
top[v] = v == adj[u][0] ? top[u] : v;
dfs2(v);
}
out[u] = dfn - 1;
}
};
struct Info {
int min, p;
};
struct Tag {
int set;
void clear() {
set = -1;
}
bool exist() {
return set != -1;
}
};
Info operator + (const Info & a, const Info & b) {
return a.min < b.min ? a : b;
}
Info operator + (const Info & a, const Tag & b) {
return Info{std::max(a.min, b.set), a.p};
}
Tag operator + (const Tag & a, const Tag & b) {
return Tag{std::max(a.set, b.set)};
}
struct SegmentTree {
struct Node {
int l, r;
Info info;
Tag tag;
};
std::vector<Node> tr;
SegmentTree(){}
SegmentTree(int n) {
tr.assign(n << 2, {});
build(0, n - 1);
}
void pushup(int u) {
tr[u].info = tr[u << 1].info + tr[u << 1 | 1].info;
}
void pushdown(Node & u, const Tag & tag) {
u.info = u.info + tag;
u.tag = u.tag + tag;
}
void pushdown(int u) {
if (tr[u].tag.exist()) {
pushdown(tr[u << 1], tr[u].tag);
pushdown(tr[u << 1 | 1], tr[u].tag);
tr[u].tag.clear();
}
}
void build(int l, int r, int u = 1) {
tr[u] = {l, r};
if (l == r) {
return;
}
int mid = l + r >> 1;
build(l, mid, u << 1);
build(mid + 1, r, u << 1 | 1);
}
void modify(int l, int r, const Tag & tag, int u = 1) {
if (l <= tr[u].l && tr[u].r <= r) {
pushdown(tr[u], tag);
return;
}
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) {
modify(l, r, tag, u << 1);
}
if (r > mid) {
modify(l, r, tag, u << 1 | 1);
}
pushup(u);
}
Info query(int l, int r, int u = 1) {
if (l <= tr[u].l && tr[u].r <= r) {
return tr[u].info;
}
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (r <= mid) {
return query(l, r, u << 1);
} else if (l > mid) {
return query(l, r, u << 1 | 1);
}
return query(l, r, u << 1) + query(l, r, u << 1 | 1);
}
void set(int p, const Info & info) {
int u = 1;
while (tr[u].l != tr[u].r) {
int mid = tr[u].l + tr[u].r >> 1;
if (p <= mid) {
u <<= 1;
} else {
u = u << 1 | 1;
}
}
tr[u].info = info;
u >>= 1;
while (u) {
pushup(u);
u >>= 1;
}
}
};
void solve() {
int n;
std::cin >> n;
std::vector<int> p(n), pos(n);
for (int i = 0; i < n; ++ i) {
std::cin >> p[i];
-- p[i];
pos[p[i]] = i;
}
HLD tr(n);
std::vector<std::vector<int>> adj(n);
for (int i = 1; i < n; ++ i) {
int u, v;
std::cin >> u >> v;
-- u, -- v;
adj[u].push_back(v);
adj[v].push_back(u);
tr.addEdge(u, v);
}
tr.work(p[0]);
SegmentTree tr1(n);
for (int i = 0; i < n; ++ i) {
tr1.set(tr.in[p[i]], Info{i, p[i]});
}
std::vector<int> ans(n, -1);
const int inf = 1e9;
auto dfs = [&](auto & self, int l, int r, int fa) -> void {
while (true) {
auto [max, u] = tr1.query(l, r);
if (max >= n || u == -1) {
break;
}
ans[u] = fa;
for (auto & v : adj[u]) {
if (tr.deep[v] > tr.deep[u] && tr1.query(tr.in[v], tr.in[v]).min < n) {
self(self, tr.in[v], tr.out[v], u);
}
}
tr1.modify(tr.in[u], tr.out[u], Tag{inf});
for (auto & v : adj[u]) {
if (tr.deep[v] < tr.deep[u] && tr1.query(tr.in[v], tr.in[v]).min < n) {
self(self, l, r, u);
break;
}
}
}
};
dfs(dfs, 0, n - 1, -1);
for (int i = 0; i < n; ++ i) {
std::cout << ans[i] + 1 << " \n"[i == n - 1];
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}

浙公网安备 33010602011771号