返回顶部

Codeforces Round #768 (Div. 2) 题解

D F未补

A. Min Max Swap

题目描述:给你两个长度为\(n\)的正整数数组\(a ,b\),你可以执行以下操作任意次:

  • 找到一个下标\(i\)
  • 交换\(a_i\)\(b_i\)的值

求执行操作后的数组\(a\)的最大值与数组\(b\)的最大值的乘积的最小值。

思路:将所有\(a_i > b _i\)的位置的两元素交换位置,然后分别求两数组的最大值再相乘即可得乘积最小。

时间复杂度:\(O(n)\)

参考代码:

void solve() {
	int n;
	cin >> n;
	vector<int>a(n + 1, 0), b(n + 1, 0);
	for (int i = 1; i <= n; ++i) cin >> a[i];
	for (int i = 1; i <= n; ++i) cin >> b[i];
	for (int i = 1; i <= n; ++i) if (a[i] > b[i]) swap(a[i], b[i]);
	int res = (*max_element(a.begin() + 1, a.end())) * (*max_element(b.begin() + 1, b.end()));
	cout << res << '\n';
	return;
}

B. Fun with Even Subarrays

题目描述:给你一个长度为\(n\)的数组\(a\),每次一个选择一个左边界\(lr\)和一个长度\(k\),满足\(lr + 2 *k - 1 \leq n\)。然后将\(a_{lr + k + i}\)的值赋值给\(a_{lr + i} ,0 \leq i < k\)。问将数组所有元素变相等的最小操作次数。

思路:显然最终数组的所有元素的值都等于\(a_n\),故按照\(a_n\)进行贪心即可。为了好操作,将数组翻转一下从前往后遍历贪心。

时间复杂度:\(O(n)\)

参考代码:

void solve() {
	int n;
	cin >> n;
	vector<int>a(n + 1, 0);
	int cnt = 0, res = 0;
	for (int i = 1; i <= n; ++i) cin >> a[i];
	reverse(a.begin() + 1, a.end());
	for (int i = 1; i <= n; ++i) {
		if (i <= cnt) continue;
		if (a[i] == a[1]) {
			++cnt;
			continue;
		}
		++res;
		cnt <<= 1;
	}
	cout << res << '\n';
	return;
}

C. And Matching

题目描述:给你一个长度为\(n\)的排列\(0 \sim n - 1\),保证\(n = 2^i (\exists0 \leq i < \infin)\)。将这个排列中的数分成\(\frac{n}{2}\)组,再给定正整数\(k\),假定每一组的元素由\((a , b)\)表示,若存在一种分组方式使得等式:

\[\sum\limits_{i = 1}^{\frac{n}{2}}a_i \&b_i = k \]

成立,则输出这种分组方式,否则输出\(-1\)

思路:注意到\(i \&(n - i - 1) = 0\),那么若\(k \neq n - 1\),那么可以将\(k\)\(0\)交换位置让\(k\)\(n - 1\)组成一对,就可以满足题设。当\(k = n - 1\)时,我们可以这样构造,让$ n - 1\(和\)n - 2$为一组,那么还差\(1\),我们可以让\(1\)\(3\)为一组,然后让\(0\)和$ n - 4$一组,\(2\)\(n - 3\)一组剩下的还是按照\(i\)与$ n - i - 1\(一组,那么就可以使得分组后满足题设,注意到\)n = 4,k = 3$时不能这样划分,需要特判。

时间复杂度:\(O(n)\)

参考代码:

void solve() {
	int n, k;
	cin >> n >> k;
	if (k == n - 1) {
		if (n == 4) {
			cout << -1 << '\n';
			return;
		}
		--n;
		cout << n - 1 << ' ' << n << '\n';
		cout << 0 << ' ' << n - 3 << '\n';
		cout << 1 << ' ' << 3 << '\n';
		cout << 2 << ' ' << n - 2 << '\n';
		for (int i = 4; i < (n + 1) / 2; ++i) cout << i << ' ' << n - i << '\n';
		return;
	}
	vector<vector<int>>res;
	int idx = 0;
	for (int i = 0; i < n / 2; ++i) {
		res.push_back({ i , n - i - 1 });
		if (i == k) idx = res.size() - 1;
		if (n - i - 1 == k) idx = res.size() - 1;
	}
	res[0][0] = k;
	if (res[idx][0] == k) res[idx][0] = 0;
	else res[idx][1] = 0;
	for (auto re : res) cout << re[0] << ' ' << re[1] << '\n';
	return;
}

E. Paint the Middle

题目描述:给你两个长度为\(n\)的序列\(a\)\(c\),初始时\(c_i = 0\;\forall 1 \leq i \leq n\)。你可以执行以下操作任意次:

  • 选择三个下标\(i , j , k(1 \leq i < j < k \leq n)\)
  • \(a_i = a_k\)
  • \(c_i = c_j = c_k = 0\)
  • \(c_j\)赋值成\(1\)

\(\sum\limits_{i = 1}^{n}c_i\)的最大值。

数据范围:\(3 \leq n \leq 2\times 10^5 , 1 \leq a_i \leq n\)

思路:我们可以在读入的时候使用last数组记录一下每个数字出现的最后的下标,考虑从前往后遍历,如果当前枚举的元素\(a_i\)最后一次的出现位置为\(j\),则说明区间\((i , j)\)内的值都可以取得,如果\((i , j)\)存在\(k\),使得\(a_{last[k]} > j\),那么\((j , k)\)之间的元素也都可以取得,那么为了让取得的区间最大,就需要找到下标最大的位置,所以我们在遍历\((i , j)\)之间的元素的时候,应该记录一下其对应元素最后一次出现的的位置的最大值,也即

\[\mathop{max}\limits_{k = i + 1}^{j - 1} last[a_k] \]

在当前区间被枚举完的时候,使用该值去更新新的区间。

时间复杂度:\(O(n)\)

参考代码:

void solve() {
	int n;
	cin >> n;
	vector<int>last(n + 1, 0), a(n + 1, 0);
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
		last[a[i]] = i;
	}
	int res = 0, pre = 0, cur = 0;
	for (int i = 1; i <= n; ++i) {
		pre = max(pre, last[a[i]]);
		if (cur > i) ++res;
		else cur = pre;
	}
	cout << res << '\n';
	return;
}
posted @ 2022-01-28 15:01  cherish-lgb  阅读(180)  评论(0编辑  收藏  举报