Codeforces 777 题目研讨

本文将研讨 Codeforces 777(Codeforces Round 401 (Div. 2))中的题目 A-E。

题目连接

A B C D E

题目分析

A

难度:普及−
题面翻译:

给你三张牌:\(0\)\(1\)\(2\)
最初选一张,然后依次进行 \(n\) 次交换,交换规则为:中间一张和左边的一张,中间一张和右边一张,中间一张和左边一张……
最后问选的那张在哪个位置。

算法标签:模拟、枚举、构造、数学
题解:
注意到每 \(6\) 次操作为一个周期,在经过一周期后序列被还原成操作前的形态。因此打个表然后直接做就做完了。


B

难度:普及−
题面翻译:

有两个人 S 和 M,他们每人有一段长度为 \(N\) 的数字,两个人在每一轮游戏中都可以按顺序拿出一个数字,谁的数字小谁就接受一次惩罚。若相等两者都没有惩罚。另外,M 可以重新安排自己数字的顺序,问 M 的最少被惩罚次数和 S 的最多被惩罚次数是多少。

算法标签:博弈论、数据结构、贪心、排序
题解:
先排个序。
参考这个博弈论的现实应用,可以得出以下策略:

  1. 若此轮能赢,则使用最小的能赢下来的牌。
  2. 若此轮不能赢,则出当前最小的牌。

证明略。
设 S 的牌为 \(s_i\),M 的牌为 \(m_i\)。显然,对于第一问,只要 \(s_i \ge m_i\) 就能出 \(m_i\) 这张牌。
而对于第二问,出牌条件改为 \(s_i>m_i\) 即可。


C

难度:普及+/提高
题面翻译:

给出一个 \(n\times m\) 的矩阵。对于第 \(j\) 列,如果满足 \(\forall i \in [1,n-1],a_{i,j} \leq a_{i+1,j}\),则称这一列是不下降的。
\(k\) 次询问,问如果只保留矩阵的第 \(L\)~\(R\) 行,矩阵中是否存在不下降的一列。

算法标签:二分、数据结构、动态规划、贪心、双指针
题解:
注意到本题和最长不下降子串有关。用 \(b_{i,j}\) 表示以 \(a_{i, j}\) 结尾的最长不下降子串起始位置的下标(注意不是长度),然后预处理出每一行最小的 \(b\) 即可在 \(O(1)\) 内查询。

#include <bits/stdc++.h>
using namespace std;
int n, m, k, a[200010], b[200010], c[200010];
int coordinate(int x, int y) {
	return x * m + y;
}
int main() {
	memset(c, 0x3f, sizeof(c));
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			scanf("%d", &a[coordinate(i, j)]);
		}
	}
	for (int j = 1; j <= m; j++) {
		for (int i = 1; i <= n; i++) {
			b[coordinate(i, j)] = ((a[coordinate(i, j)] < a[coordinate(i - 1, j)]) ? i : b[coordinate(i - 1, j)]);
		}
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			c[i] = min(c[i], b[coordinate(i, j)]);
		}
	}
	scanf("%d", &k);
	while (k--) {
		int l, r;
		scanf("%d%d", &l, &r);
		if (c[r] <= l) puts("Yes");
		else puts("No");
	}
	return 0;
}

D

难度:普及+/提高
题面翻译:

给出 \(n\) 个开头是 # 的字符串,你需要通过操作使这些字符串按字典序从小到大排列。
每次操作,你可以把某一个字符串的任意后缀去掉(当然你甚至可以去掉整个字符串),要求你去掉的字符数量最少。
输出你操作完的这 \(n\) 个字符串。

算法标签:二分、贪心、字符串
题解:
不难发现,本题和排序没有任何关系
注意到去除后缀会使字符串的字典序变小,因此 \(s_n\) 不需要变化。而对于 \(s_i(1\le i\le n-1)\),可以贪心的操作,使其刚好小于等于 \(s_{i+1}\)。因此从后往前操作即可。

#include <bits/stdc++.h>
using namespace std;
int n;
string s[500010];
int main() {
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> s[i];
	for (int i = n - 1; i >= 1; i--) {
		int t = -1;
		for (int j = 0; j < s[i].size(); j++) {
			if (j == s[i + 1].size()) {
				t = j;
				break;
			}
			if (s[i][j] < s[i + 1][j]) break;
			if (s[i][j] > s[i + 1][j]) {
				t = j;
				break;
			}
		}
		if (t != -1) s[i].erase(t);
	}
	for (int i = 1; i <= n; i++) cout << s[i] << "\n";
	return 0;
}

E

难度:提高+/省选−
题面翻译:

给出 \(n\) 个环形零件的厚度、内径、外径,要求按以下条件装配若干圆盘后,形成的塔最高。

  • 圆盘应平放。也就是说,对于每个安放的零件,侧视图应当为长方形,而俯视图应为圆环。
  • 圆盘应叠放,而下方的圆盘外径应大于等于上方圆盘的外径。
  • 上方圆盘的外径应严格大于下方圆盘的内径(否则上方圆盘会因重力作用掉到下方圆盘的孔里)。

算法标签:枚举、排序
题解:
这道题和最长上升子序列有点像。先按照 \(b_i\) 降序对圆环排序,方便转移。
\(\text{dp}_i\) 为以 \(i\) 为顶的最大塔高,则有转移:

\[\text{dp}_i=\text{max}\{\text{dp}_j+h_i\} (j < i, b_i > a_j) \]

。于是你 T 了。
因此考虑引入堆优化。将先前的状态压入大根堆中(按 \(h\) 排序),直接弹出 \(a \ge b_i\) 的栈顶(因为此时堆顶不合法,接下来堆顶一定更不合法),直到合法,然后更新答案即可。
时间复杂度 \(O(n\text{log}{n})\),可以通过。

#include <bits/stdc++.h>
using namespace std;
#define int long long
struct dat {
	int a, b, h;
} ring[100010];
int n, dp[100010];
bool cmp(dat a, dat b) {
	if (a.b != b.b) return a.b > b.b;
	return a.a > b.a;
}
bool operator < (dat x, dat y) { return x.h < y.h; }
priority_queue<dat> q;
signed main() {
	scanf("%lld", &n);
	for (int i = 1; i <= n; i++) {
		scanf("%lld%lld%lld", &ring[i].a, &ring[i].b, &ring[i].h);
	}
	sort(ring + 1, ring + n + 1, cmp);
	q.push({0, 0, 0});
	long long ans = 0;
	for (int i = 1; i <= n; i++) {
		while (q.top().a >= ring[i].b) q.pop();
		dp[i] = q.top().h + ring[i].h;
		q.push({ring[i].a, ring[i].b, q.top().h + ring[i].h});
		ans = max(ans, dp[i]);
	}
	printf("%lld\n", ans);
	return 0;
}

总结

这套题在 Codeforces 中属于较简单水平,有希望考场 AK(只是有希望)。


参考资料
[1] Sima Qian.The Biography of Sun Wu & Wu Qi[A]Records of the Grand Historian[C].Chang'an:Yang Yun,91BCE:1-2

posted @ 2024-11-11 09:47  cakn  阅读(134)  评论(1编辑  收藏  举报