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;
}
posted @ 2025-07-11 17:59  maburb  阅读(546)  评论(0)    收藏  举报