Codeforces 777 题目研讨

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

题目连接

A B C D E

题目分析

A

难度:普及−
题面翻译:

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

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


B

难度:普及−
题面翻译:

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

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

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

证明略。
设 S 的牌为 si,M 的牌为 mi。显然,对于第一问,只要 simi 就能出 mi 这张牌。
而对于第二问,出牌条件改为 si>mi 即可。


C

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

给出一个 n×m 的矩阵。对于第 j 列,如果满足 i[1,n1],ai,jai+1,j,则称这一列是不下降的。
k 次询问,问如果只保留矩阵的第 L~R 行,矩阵中是否存在不下降的一列。

算法标签:二分、数据结构、动态规划、贪心、双指针
题解:
注意到本题和最长不下降子串有关。用 bi,j 表示以 ai,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 个字符串。

算法标签:二分、贪心、字符串
题解:
不难发现,本题和排序没有任何关系
注意到去除后缀会使字符串的字典序变小,因此 sn 不需要变化。而对于 si(1in1),可以贪心的操作,使其刚好小于等于 si+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 个环形零件的厚度、内径、外径,要求按以下条件装配若干圆盘后,形成的塔最高。

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

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

dpi=max{dpj+hi}(j<i,bi>aj)

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

#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 @   cwkapn  阅读(138)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示