VP The 15th Shandong CCPC Provincial Collegiate Programming Contest


A. Project Management

题意:有\(n\)个人,第\(i\)个人有\(a_i, b_i\)两个属性,代表他可以接受最多\(b_i\)\(a\)值大于\(a_i\)的人。求最多选多少人。

把相同\(a\)值的人存到一起,按\(b\)值从小到大排序。
那么可以从大到小枚举\(a\),记拿了\(k\)个比\(a\)值比\(a_i\)大的数。那么如果想拿\(x\)\(a_i\)的数,拿\(b\)最大的前\(x\)个是最优的。依次枚举\(x\),可以得到需要删掉后面多少个,那么选择使人数增加最多的方案。因为前面的\(a\)都比当前选择的人的小,那么在前面的人眼里后面的人都是一样的,那么我们只需要保证人数最多就行。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	int n;
	std::cin >> n;
	std::vector<std::vector<std::pair<int, int>>> A(n + 1);
	for (int i = 0; i < n; ++ i) {
		int a, b;
		std::cin >> a >> b;
		A[a].emplace_back(b, i);
	}

	std::vector<int> ans;
	for (int i = n; i >= 1; -- i) {
		std::ranges::sort(A[i]);
		int m = A[i].size();
		int p = -1, max = 0;
		for (int j = 0; j < m; ++ j) {
			int sub = std::max(0, (int)ans.size() - A[i][j].first);
			int add = m - j;
			if (add - sub > max) {
				max = add - sub;
				p = j;
			}
		}

		if (p != -1) {
			int sub = std::max(0, (int)ans.size() - A[i][p].first);
			while (sub -- ) {
				ans.pop_back();
			}

			for (int j = p; j < m; ++ j) {
				ans.push_back(A[i][j].second);
			}
		}
	}

	std::cout << ans.size() << "\n";
	for (auto & x : ans) {
		std::cout << x + 1 << " \n"[x == ans.back()];
	}
}

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. Bracket Integer

题意:给出一个数字,求小于等于这个数的最大合法括号数。一个括号数就是按数字分为不同的括号可以匹配的括号序列。

考虑枚举这个数和原始相同的前缀,然后枚举下一个数放什么,计算栈里剩下的元素数,这些后面肯定是一一对应的,然后中间放偶数个\(9\)。然后前缀越长这个数肯定越大,前缀相同的情况下肯定让下一个放更大的数。于是枚举出合法的最大的位置就行。然后模拟一下得到答案。没有合法的位置就输出偶数个\(9\)。长度小于\(n\)

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	std::string s;
	std::cin >> s;
	int n = s.size();
	int p = -1;
	char ch = '0';
	std::string stk;
	stk.reserve(n);
	for (int i = 0; i < n; ++ i) {
		for (char c = s[i] - 1; c >= '0' + (i == 0); -- c) {
			int x = stk.size();
			if (x && c == stk.back()) {
				-- x;
			} else {
				++ x;
			}

			int y = n - 1 - i;
			if (y >= x && (y - x) % 2 == 0) {
				p = i;
				ch = c;
				break;
			}
		}

		if (stk.size() && s[i] == stk.back()) {
			stk.pop_back();
		} else {
			stk.push_back(s[i]);
		}
	}

	if (stk.empty()) {
		std::cout << s << "\n";
		return;
	}

	if (p == -1) {
		std::string ans;
		ans.reserve(n);
		while (ans.size() + 2 < n) {
			ans += "99";
		}
		std::cout << ans << "\n";
	} else {
		stk.clear();
		std::string ans;
		ans.reserve(n);
		for (int i = 0; i < p; ++ i) {
			ans += s[i];
			if (stk.size() && s[i] == stk.back()) {
				stk.pop_back();
			} else {
				stk.push_back(s[i]);
			}
		}

		ans += ch;

		if (stk.size() && ch == stk.back()) {
			stk.pop_back();
		} else {
			stk.push_back(ch);
		}

		int cnt = (n - 1 - p - stk.size()) / 2;
		for (int i = 0; i < cnt; ++ i) {
			ans += "99";
		}

		while (stk.size()) {
			ans += stk.back();
			stk.pop_back();
		}
		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;
}

D. Distributed System

题意:一个长度为\(n\)的数组,\(q\)个操作,每次给出\(a_i, b_i\),使得所有\(x \% n\)的位置加一,\(x \in [b_i, b_i + a_i]\)

差分一下就行。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	int n, q;
	std::cin >> n >> q;
	std::vector<i64> d(2 * n);
	i64 sum = 0;
	while (q -- ) {
		int a, b;
		std::cin >> a >> b;
		sum += a / n;
		a %= n;
		d[b] += 1;
		d[b + a] -= 1;
	}

	std::vector<i64> ans(n);
	for (int i = 1; i + 2 < 2 * n; ++ i) {
		d[i] += d[i - 1];
	}

	for (int i = 0; i + 2 < 2 * n; ++ i) {
		ans[i % n] += d[i];
	}

	for (int i = 0; i < n; ++ i) {
		std::cout << ans[i] + sum << " \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;
}

E. Greatest Common Divisor

题意:一个数组,恰好\(k\)次操作。每次把一个数加一。求最后数组的最大公约数。\(\sum max_{a_i} \leq 1e6\)

设最后数组变为\(b\),最大公约数为\(x\),那么因为\(b_i\)都是\(x\)的倍数,那么\(sum_b\)也是\(x\)的倍数,也就是\(sum_a + k\)\(x\)的倍数。
那么可以枚举这个\(x\)。则每个数先变为最近的\(x\)的倍数,然后剩下的操作数是\(x\)的倍数。
然后观察最大和的限制这个性质,考虑开个桶记录一下每个数的个数,然后预处理前缀和得到前\(i\)个数有多少个,以及它们的和。那么如果\(x \geq max_{a_i}\),可以直接计算至少需要加多少使得所有数是\(x\)的倍数。如果\(x \leq max_{a_i}\)。因为\([1, max_{a_i}]\)的每个数都只会算一次,那么可以用\(\lfloor \frac{max_{a_i}}{x} \rfloor\)的时间枚举每个\([x^{i-1} + 1, x^i - 1]\)的数需要多少操作。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	int n;
	i64 k;
	std::cin >> n >> k;
	std::vector<i64> a(n);
	for (int i = 0; i < n; ++ i) {
		std::cin >> a[i];
	}

	i64 sum = std::accumulate(a.begin(), a.end(), 0ll);
	i64 max = std::ranges::max(a);

	std::vector<i64> cnt(max + max + 1), s(max + max + 1);
	for (auto & x : a) {
		++ cnt[x];
	}

	for (int i = 1; i <= 2 * max; ++ i) {
		s[i] = cnt[i] * i + s[i - 1];
		cnt[i] += cnt[i - 1];
	}

	i64 ans = 1;
	auto get = [&](i64 x) -> void {
		if (x <= ans) {
			return;
		}

		if (x > max) {
			i64 t = n * x - sum;
			if (t <= k && (k - t) % x == 0) {
				ans = x;
			}
			return;
		}

		i64 t = 0;
		for (i64 i = 0, j = x; i <= max; i += x, j += x) {
			t += (cnt[j - 1] - cnt[i]) * j - (s[j - 1] - s[i]);
			if (t > k) {
				return;
			}
		}

		if ((k - t) % x) {
			return;
		}

		ans = x;
	};

	for (i64 i = 1; i * i <= sum + k; ++ i) {
		if ((sum + k) % i == 0) {
			get(i);
			if (i * i != (sum + k)) {
				get((sum + k) / i);
			}
		}
	}
	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;
}

G. Assembly Line

题意:一个长度为\(k\)的流水线,每个人位置一个时刻最多处理一个物品,把这个物品丢到下一个位置。有\(n\)个操作,每次在\(t_i\)时刻在\(w_i\)个位置加一个物品。求所有物品处理完需要的时间。

如果不考虑两个物品同时在一个地方的情况,则第\(i\)个物品的时间为\(a_i = t_i + k - w_i\)
那么考虑从小到大处理,记\(b_i\)为处理前\(i\)个需要的时间,那么\(b_i = \max(b_{i-1} + 1, a_i)\)

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	int n, k;
	std::cin >> n >> k;
	std::vector<int> a(n);
	for (int i = 0; i < n; ++ i) {
		int w, t;
		std::cin >> w >> t;
		a[i] = k - w + t;
	}

	std::ranges::sort(a);
	for (int i = 1; i < n; ++ i) {
		a[i] = std::max(a[i], a[i - 1] + 1);
	}
	std::cout << a.back() << "\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. Minimum Spanning Tree

题意:一个树,你还可以最多加\(k\)条边,边权为\(|u - v|\),求最小生成树。

优先考虑权值为\(0\)的边。
然后考虑加边,如果\(k \leq n -1\),那么显然可以加\((i, i + 1)\)这样的边,不算原图中权值为\(0\)的边的话这些边组成的最小生成树就是最小的。
那么反过来想,我们就加这\(n-1\)条边,多出来的考虑删去,那么可以把原图中前\(n - 1 - k\)小的边加进来。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

struct DSU {
	std::vector<int> fa;
	DSU(int n) {
		fa.assign(n, 0);
		std::ranges::iota(fa, 0);
	}

	int find(int x) {
		return x == fa[x] ? x : fa[x] = find(fa[x]);
	}

	bool merge(int x, int y) {
		int u = find(x), v = find(y);
		if (u == v) {
			return false;
		}

		fa[v] = u;
		return true;
	}
};

void solve() {
	int n, m, k;
	std::cin >> n >> m >> k;
	std::vector<std::tuple<int, int, int, int>> edges(m);
	for (int i = 0; i < m; ++ i) {
		int u, v, w;
		std::cin >> u >> v >> w;
		-- u, -- v;
		edges[i] = {w, i, u, v};
	}

	std::ranges::sort(edges);
	DSU dsu(n);
	std::vector<int> ans;
	i64 sum = 0;
	int t = 0, cnt = std::max(0, n - 1 - k);
	for (int i = 0; i < m && t < n - 1; ++ i) {
		auto & [w, id, u, v] = edges[i];
		if (w == 0) {
			if (dsu.merge(u, v)) {
				ans.push_back(id);
				++ t;
			}
			continue;
		}

		if (t >= cnt) {
			break;
		}

		if (dsu.merge(u, v)) {
			ans.push_back(id);
			sum += w;
			++ t;
		}
	}

	std::vector<std::pair<int, int>> add;
	for (int i = 0, id = m; i + 1 < n && t < n - 1; ++ i) {
		if (dsu.merge(i, i + 1)) {
			add.emplace_back(i, i + 1);
			ans.push_back(id);
			id += 1;
			sum += 1;
			++ t;
		}
	}

	std::cout << add.size() << "\n";
	for (auto & [u, v] : add) {
		std::cout << u + 1 << " " << v + 1 << "\n";
	}

	std::cout << sum << "\n";
	for (auto & id : ans) {
		std::cout << id + 1 << " \n"[id == ans.back()];
	}
}

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. Square Puzzle

题意:一个长度为\(9\)的排列构成一个\(3 \times 3\)的矩阵。每从可以把一行右移,一列下移,以及整个矩阵顺时针旋转。给出两个矩阵,求第一个矩阵变成第二个矩阵的最小操作数。

考虑把数字看作标识,每个格子的编为为\(i \times 3 + j\)。那么每个矩阵其实都是按顺序从\(1\)\(9\)的矩阵。然后每个数字有一个标识。则可以建立映射关系,也就是每个标识代表哪个格子。这样就可以把第一个矩阵和\(1\)\(9\)这个矩阵关联起来,同时用这个映射关系变换第二个矩阵。那么问题变为从\(1\)\(9\)这个矩阵变为\(b\)需要的最小步数。每个询问的起点就都是相同的,可以预处理。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

std::map<std::string, int> mp;

void init() {
	std::string a = "123456789";
	auto getr = [&](std::string s, int i) -> std::string {
		char c = s[i * 3 + 2];
		s[i * 3 + 2] = s[i * 3 + 1];
		s[i * 3 + 1] = s[i * 3 + 0];
		s[i * 3 + 0] = c;
		return s;
	};

	auto getd = [&](std::string s, int j) -> std::string {
		char c = s[6 + j];
		s[6 + j] = s[3 + j];
		s[3 + j] = s[j];
		s[j] = c;
		return s;
	};

	auto rotate = [&](std::string & s) -> std::string {
		std::string t = s;
		for (int i = 0; i < 3; ++ i) {
			for (int j = 0; j < 3; ++ j) {
				t[j * 3 + 3 - 1 - i] = s[i * 3 + j];
			}
		}
		return t;
	};

	std::queue<std::string> q;
	q.push(a);
	mp[a] = 0;
	while (q.size()) {
		auto s = q.front(); q.pop();
		for (int i = 0; i < 3; ++ i) {
			auto t = getr(s, i);
			if (!mp.count(t)) {
				mp[t] = mp[s] + 1;
				q.push(t);
			}
		}

		for (int i = 0; i < 3; ++ i) {
			auto t = getd(s, i);
			if (!mp.count(t)) {
				mp[t] = mp[s] + 1;
				q.push(t);
			}
		}

		auto t = rotate(s);
		if (!mp.count(t)) {
			mp[t] = mp[s] + 1;
			q.push(t);
		}
	}
}

void solve() {
	std::string a, b;
	for (int i = 0; i < 3; ++ i) {
		std::string t;
		std::cin >> t;
		a += t;
	}

	for (int i = 0; i < 3; ++ i) {
		std::string t;
		std::cin >> t;
		b += t;
	}
	
	std::array<int, 10> to{};
	for (int i = 0; i < 9; ++ i) {
		to[a[i] - '0'] = i + 1;
	}

	for (int i = 0; i < 9; ++ i) {
		b[i] = to[b[i] - '0'] + '0';
	}

	if (mp.count(b)) {
		std::cout << mp[b] << "\n";
	} else {
		std::cout << -1 << "\n";
	}
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t = 1;
	init();
	std::cin >> t;
	while (t -- ) {
		solve();
	}
	return 0;
}

L. Stella

签到题

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	std::map<char, int> mp;
	std::string s = "OBAFGKM";
	for (int i = 0; i < 7; ++ i) {
		mp[s[i]] = 7 - i;
	}

	auto get = [&](std::string s) -> std::string {
		std::string res;
		res += mp[s[0]];
		res += (9 - (s[1] - '0')) + '0';
		return res;
	};

	std::string a, b;
	std::cin >> a >> b;
	a = get(a), b = get(b);
	if (a > b) {
		std::cout << "hotter\n";
	} else if (a == b) {
		std::cout << "same\n";
	} else {
		std::cout << "cooler\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;
}
posted @ 2025-08-22 22:27  maburb  阅读(88)  评论(0)    收藏  举报