Codeforces 777 题目研讨
本文将研讨 Codeforces 777(Codeforces Round 401 (Div. 2))中的题目 A-E。
题目连接
题目分析
A
难度:普及−
题面翻译:
给你三张牌:\(0\),\(1\),\(2\)。
最初选一张,然后依次进行 \(n\) 次交换,交换规则为:中间一张和左边的一张,中间一张和右边一张,中间一张和左边一张……
最后问选的那张在哪个位置。
算法标签:模拟、枚举、构造、数学
题解:
注意到每 \(6\) 次操作为一个周期,在经过一周期后序列被还原成操作前的形态。因此打个表然后直接做就做完了。
B
难度:普及−
题面翻译:
有两个人 S 和 M,他们每人有一段长度为 \(N\) 的数字,两个人在每一轮游戏中都可以按顺序拿出一个数字,谁的数字小谁就接受一次惩罚。若相等两者都没有惩罚。另外,M 可以重新安排自己数字的顺序,问 M 的最少被惩罚次数和 S 的最多被惩罚次数是多少。
算法标签:博弈论、数据结构、贪心、排序
题解:
先排个序。
参考这个博弈论的现实应用,可以得出以下策略:
- 若此轮能赢,则使用最小的能赢下来的牌。
- 若此轮不能赢,则出当前最小的牌。
证明略。
设 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\) 为顶的最大塔高,则有转移:
。于是你 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(只是有希望)。