2018 China Collegiate Programming Contest - Guilin Site


A. Array Merge

题意:给你两个数组,你要把它们合并为一个数组,保持原数组的元素顺序不变。合并后为\(c\),求\(\sum_{i=1}^{n+m} c_i \times i\)最小。

贪心想哪个大就放哪个,但这样如果某个数组前面很小,后面非常大,这个做法就是错的。
然后想到按平均值来看,但不能把一整个还没放进去的一起算平均值,因为一起算代表想把它们一起放进去,但我们可能一次只放一段进去。那么我们怎么分段呢?肯定希望让这一段平均值更大,那么我们可以对两个数组分开处理,把他们切成一段一段的子数组,这些子数组平均值从大到小排列,这样就变成了归并排序。
关于具体怎么分段,我们可以先让每个数单独分段,然后去合并相邻的段,如果后面的平均值比当前段大,那么应该合并。这样一直操作到没有可以合并的为止。

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

using i64 = long long;

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

	for (int i = 0; i < m; ++ i) {
		std::cin >> b[i];
	}

	auto work = [&](std::vector<i64> & a) -> std::vector<std::tuple<i64, int, int>> {
		int n = a.size();
		std::vector<std::tuple<i64, int, int>> res;
		for (int i = 0; i < n; ++ i) {
			res.emplace_back(a[i], i, i);
		}

		while (res.size() > 1) {
			bool flag = false;
			std::vector<std::tuple<i64, int, int>> nres;
			nres.push_back(res[0]);
			for (int i = 1; i < res.size(); ++ i) {
				auto & [s1, l1, r1] = nres.back();
				auto & [s2, l2, r2] = res[i];
				if (s1 * (r2 - l2 + 1) <= s2 * (r1 - l1 + 1)) {
					nres.back() = {s1 + s2, l1, r2};
					flag = true;
				} else {
					nres.push_back(res[i]);
				}
			}

			res = nres;
			if (!flag) {
				break;
			}
		}

		return res;
	};

	auto A = work(a);
	auto B = work(b);
	i64 ans = 0, id = 1;
	auto get = [&](std::vector<i64> & a, int l, int r) -> void {
		for (int i = l; i <= r; ++ i) {
			ans += a[i] * id;
			++ id;
		}
	};
	for (int i = 0, j = 0; i < A.size() || j < B.size();) {
		if (i == A.size()) {
			auto & [_, l, r] = B[j];
			get(b, l, r);
			++ j;
		} else if (j == B.size()) {
			auto & [_, l, r] = A[i];
			get(a, l, r);
			++ i;
		} else {
			auto & [s1, la, ra] = A[i];
			auto & [s2, lb, rb] = B[j];
			if (s1 * (rb - lb + 1) >= s2 * (ra - la + 1)) {
				get(a, la, ra);
				++ i;
			} else {
				get(b, lb, rb);
				++ j;
			}
		}
	}
	std::cout << ans << "\n";
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t;
	std::cin >> t;
	for (int i = 1; i <= t; ++ i) {
		std::cout << "Case " << i << ": ";
		solve();
	}
	return 0;
}

D. Bits Reverse

题意:给你两个数\(x, y\),每次你可以把一个数二进制下的连续三位翻转。求\(x\)变成\(y\)的最小操作数。

三位翻转其实中间那一位并没有变。所以是奇数位置和奇数位置交换,偶数位置和偶数位置交换。分奇偶讨论,就变成了两个\(01\)数组,每次交换一个数组两个相邻的位置,求相等的最小操作数。这个之间贪心的按顺序从小到大匹配\(1\)就行了。

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

using i64 = long long;

void solve() {
	i64 x, y;
	std::cin >> x >> y;
	std::vector<int> x0, x1, y0, y1;
	for (int i = 0; i < 60; ++ i) {
		if (x >> i & 1) {
			if (i & 1) {
				x0.push_back(i);
			} else {
				x1.push_back(i);
			}
		} 

		if (y >> i & 1) {
			if (i & 1) {
				y0.push_back(i);
			} else {
				y1.push_back(i);
			}
		} 
	}

	if (x0.size() != y0.size() || x1.size() != y1.size()) {
		std::cout << -1 << "\n";
		return;
	}

	int ans = 0;
	for (int i = 0; i < x0.size(); ++ i) {
		ans += std::abs(x0[i] - y0[i]) / 2;
	}

	for (int i = 0; i < x1.size(); ++ i) {
		ans += std::abs(x1[i] - y1[i]) / 2;
	}
	std::cout << ans << "\n";
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t;
	std::cin >> t;
	for (int i = 1; i <= t; ++ i) {
		std::cout << "Case " << i << ": ";
		solve();
	}
	return 0;
}

G. Greatest Common Divisor

题意:给你一个数组,你每次给所有数加一,求使得数组的\(gcd\)大于\(1\)的最小操作数。

先从小到大排序。假设最终数组的\(gcd\)\(d\),那么因为\(d | a_i, d | d_{i+1}\),所以\(d | a_{i+1} - a_i\)。而\(a_{i+1} - a_i\)不管怎么操作都是不变的,那么\(d\)一定是所有相邻两个数差值的\(gcd\)。枚举这个\(gcd\)的因子取操作数最小的就行。
特殊情况就是所有数都相同,那么是\(1\)就需要加一,否则不需要操作。

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

using i64 = long long;

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

	if (n == 1) {
		std::cout << (a[0] == 1) << "\n";
		return;
	}

	if (d != 1) {
		std::cout << 0 << "\n";
		return;
	}

	std::ranges::sort(a);

	d = 0;
	for (int i = 1; i < n; ++ i) {
		d = std::gcd(d, a[i] - a[i - 1]);
	}

	if (d == 0) {
		std::cout << (a[0] == 1) << "\n";
		return;
	}

	if (d == 1) {
		std::cout << -1 << "\n";
		return;
	}

	auto get = [&](i64 x, i64 y) -> i64 {
		return (x + y - 1) / y * y;
	};

	i64 ans = 1e18;
	for (int i = 2; i <= d / i; ++ i) {
		if (d % i == 0) {
			ans = std::min(ans, get(a[0], i) - a[0]);
			ans = std::min(ans, get(a[0], d / i) - a[0]);
		}
	}

	ans = std::min(ans, get(a[0], d) - a[0]);
	std::cout << ans << "\n";
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t;
	std::cin >> t;
	for (int i = 1; i <= t; ++ i) {
		std::cout << "Case " << i << ": ";
		solve();
	}
	return 0;
}

H. Hamming Distance

题意:两个字符串的\(Hamming\)距离定义为\(a_i \ne b_i\)的数量。给你两个字符串,要求构造一个字典序最小的字符串,使得它和这两个字符串的\(Hamming\)距离相等。

贪心。
首先两个字符串相同的位置不管怎么放都无所谓,因为要么都不加距离,要么都加距离。那么直接放\(a\)就行了。
记下不同的位置的数量,然后从小到大贪心,枚举放哪个字符,讨论他对两个距离的差值的贡献,我们希望距离相等,也就是它们距离的差值为\(0\)。那么如果放了这个字符后,后面可以把差值变成\(0\),那么就可以放,找最小的放就行。

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

using i64 = long long;

void solve() {	
	std::string s, t;
	std::cin >> s >> t;
	int n = s.size();
	int cnt = 0;
	for (int i = 0; i < n; ++ i) {
		cnt += s[i] != t[i];
	}

	std::string ans;
	int diff = 0;
	for (int i = 0; i < n; ++ i) {
		if (s[i] == t[i]) {
			ans += 'a';
		} else {
			for (char c = 'a'; c <= 'z'; ++ c) {
				int ndiff = diff + (s[i] != c) - (t[i] != c);
				if (std::abs(ndiff) <= cnt - 1) {
					-- cnt;
					diff = ndiff;
					ans += c;
					break;
				}
			}
		}
	}

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

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t;
	std::cin >> t;
	for (int i = 1; i <= t; ++ i) {
		std::cout << "Case " << i << ": ";
		solve();
	}
	return 0;
}

J. Stone Game

题意:两个人博弈。有\(n\)堆石头,第\(i\)堆有\(a_i\)个石头。每次从一堆拿走一个,不能出现\(a_i = a_{i+1}\)的情况。保证初始状态合法。求谁是赢家。

\(a_1 < a_0, a_n < a_{n+1}\)
那么一定有\(a_i < a_{i-1}\)\(a_i < a_{i+1}\)的位置。这些位置一定可以拿到只剩\(0\)。那么对于它两边的石堆,剩下的数要大于它,同时也要大于它另一个相邻的数,如果求出另一个相邻数可以剩多少,那么这个数就是两个相邻数剩下最多的数加一。那么可以\(bfs\)\(a_i < a_{i-1}\)\(a_i < a_{i+1}\)的位置开始遍历,每次把左右入队,更新答案。那么总共可以拿奇数就是\(Alice\)赢。

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

using i64 = long long;

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

	a[0] = a[n + 1] = 2e9;
	std::vector<int> st(n + 2);
	st[0] = st[n + 1] = 1;
	std::queue<int> q;
	for (int i = 1; i <= n; ++ i) {
		if (a[i] < a[i - 1] && a[i] < a[i + 1]) {
			q.push(i);
			st[i] = 1;
		}
	}

	auto check = [&](int x) -> bool {
		if (st[x]) {
			return false;
		}

		return (a[x - 1] > a[x] || st[x - 1]) && (a[x + 1] > a[x] || st[x + 1]);
	};

	while (q.size()) {
		int u = q.front(); q.pop();
		int l = u - 1, r = u + 1;
		if (check(l)) {
			q.push(l);
			st[l] = 1;
			b[l] = std::max(b[l - 1], b[l + 1]) + 1;
		}

		if (check(r)) {
			q.push(r);
			st[r] = 1;
			b[r] = std::max(b[r - 1], b[r + 1]) + 1;
		}
	}

	i64 sum = 0;
	for (int i = 1; i <= n; ++ i) {
		sum += a[i] - b[i];
	}

	if (sum & 1) {
		std::cout << "Alice\n";
	} else {
		std::cout << "Bob\n";
	}
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t;
	std::cin >> t;
	for (int i = 1; i <= t; ++ i) {
		std::cout << "Case " << i << ": ";
		solve();
	}
	return 0;
}
posted @ 2025-05-14 18:30  maburb  阅读(22)  评论(0)    收藏  举报