[CP / Codeforces] Codeforces Round 923 (Div. 3) A-D
两个小时只做出来四道题。虽然是赛后补题,不过即便有赛时 buff 的加持,我感觉自己也只能做出来四道题,而且一如既往地把简单的东西搞复杂了,写了好多绕来绕去的代码。
不过我并没有灰心,只是因为经验不足,而已。
A. Make it White
分析
分别找到 'B' 的最小、最大下标,作差即得答案。
代码
略。
B. Following the String
分析
考虑如下输入:
我们可以构造出相应的答案:
这实际上是一个 “分配” 的过程:对于输入数组中的每个数字,如果它是
我打算模拟这个分配的过程。对于上面给出的输入来说分配操作是很容易实现的,只需要在每次数字发生改变的时候,将待分配字母重置为
那么排序的依据呢?由于 std::sort
是不稳定的排序,所以需要给出更严格的条件。考虑下面的输入:
排序之后变成这样:
对照的下标可能是:
那么得到的答案就变成了:
和输入发生了矛盾。问题在于,在排序时对于相同的数字,没有规定下标的先后顺序。
修改排序条件后,再逐个将分配字母放入下标对应的位置,即得答案。
代码( )
void solve() { int n; std::cin >> n; std::vector<int> v(n); std::vector<int> id(n); for (int i = 0; i < n; i++) { std::cin >> v[i]; } std::iota(id.begin(), id.end(), 0); std::sort(id.begin(), id.end(), [&](int lhs, int rhs) { return v[lhs] == v[rhs] ? lhs < rhs : v[lhs] < v[rhs]; }); std::vector<char> ans(n); int lastval = -1; char c = 'a'; for (auto e : id) { if (v[e] != lastval) { c = 'a'; } ans[e] = c++; lastval = v[e]; } for (auto c : ans) { std::cout << c; } std::cout << '\n'; }
题解学习
基本思想依然是逐个 “分配” 字母,但并不需要使用排序。
再次观察输入数组,其中的数字表示的是字母的使用次数,所以每次扫描的时候只需要遍历所有字母,找到使用次数和当前数字对应的字母,输出,然后更新使用次数即可。
改进代码( )
虽然从输入规模来看,我的代码跑得不一定比改进代码慢,但是——第一,为了存储下标消耗了大量的额外空间,第二,为了写出这样的代码,我花了许多现实意义上的时间,如果是排行榜上的那些大佬的话,估计在我做这道题的时间里都快 AK 这场比赛了。总之,我被完爆了……不过这个方法,我记住了!
void solve() { int n, t; std::cin >> n; std::string ans; std::vector<int> cnt(26); for (int i = 0; i < n; i++) { std::cin >> t; for (int j = 0; j < 26; j++) { if (cnt[j] == t) { ++cnt[j]; ans.push_back('a' + j); break; } } } std::cout << ans << '\n'; }
C. Choose the Different Ones!
分析
这道题的官方题解倒是和我的想法意外地一致。
题目的意思很简单,两个数组各取
努力压制脑中有关暴力搜索的想法,我开始思考。如果能事先确定排列中的某些数,便能缩小搜索的范围。这样的数符合什么样的条件呢?
对于两个输入数组来说,首先可以去掉
很明显,前者就是我想要寻找的 “可以被事先确定” 的数字,因为不选它们就无法构成完整的排列了。
选完它们后,再从那些共有的数字中挑选能够补全排列的数字,最后再检查取法是否符合题目要求
代码
void solve() { int n, m, k, t; std::cin >> n >> m >> k; std::vector<int> flag(k + 3); for (int i = 0; i < n; i++) { std::cin >> t; if (t <= k) { flag[t] |= 1; } } for (int i = 0; i < m; i++) { std::cin >> t; if (t <= k) { flag[t] |= 2; } } int a = 0, b = 0; for (int i = 1; i <= k; i++) { if (flag[i] == 0) { std::cout << "NO\n"; return; } if (flag[i] == 1) { ++a; flag[i] = -1; } else if (flag[i] == 2) { ++b; flag[i] = -1; } } int check = 0; for (int i = 1; i <= k; i++) { if (flag[i] != -1) { ++check; } } if (a <= k / 2 && b <= k / 2 && k - a - b == check) { std::cout << "YES\n"; } else { std::cout << "NO\n"; } }
D. Find the Different Ones!
分析
考虑反面情况,即
所以可以把输入数组划分成很多段,段内的元素都是相等的。对于每次 query,分别判断左边界和右边界对应的段序号是否一致,若一致,说明所查询区间内的元素都是相等的,输出 "-1 -1",否则选择两个段的边界输出即可。
这里的 “两个段” 的选择是有讲究的,第一是不能单纯地选择最左边的段和最右边的段,因为这两个段的元素可能是相等的,比如下面这种情况:
最简单的选法就是选择相邻的两个段,因为它们各自对应的元素必定不相同。
第二,如果选择的段有一个是最左边的段,那么应当选择这个段的右边界;如果选择的段有一个是最右边的段,那么应当选择这个段的左边界。这么做是为了保证输出的两个数字在所查询区间之内。
所以我开了两个 vector 存放区间的左右端点,每次查询用二分查找确定段序号,调了好久的 bug,最后发现问题出在区间数字是从 1 开始而不是从 0 开始的,修改之后重新 AC。
代码(
void solve() { int n, t; std::cin >> n; std::vector<int> v(n); std::vector<int> sL, sR; t = 0; std::cin >> v[0]; for (int i = 1; i < n; i++) { std::cin >> v[i]; if (v[i] != v[i - 1]) { sL.push_back(t + 1); sR.push_back(i); t = i; } } sL.push_back(t + 1); sR.push_back(n); int q, l, r; std::cin >> q; for (int i = 0; i < q; i++) { std::cin >> l >> r; auto t1 = std::distance(sL.begin(), std::upper_bound(sL.begin(), sL.end(), l)); auto t2 = std::distance(sR.begin(), std::lower_bound(sR.begin(), sR.end(), r)); if (t1 - 1 == t2) { std::cout << "-1 -1\n"; } else { std::cout << sR[t1 - 1] << ' ' << sL[t1] << '\n'; } } }
题解学习(新的分段技巧!)
在预处理时,不需要手动存放每个段的左、右端点,而是使用当前段的左端点或右端点标记相应位置上的元素,这样在后续的查询中就能以
之所以不直接用段序号来标记,是因为这样做无法在常数时间内找到当前段的上一段 / 下一段,而是需要对整个段进行扫描,直到段序号发生变化。
改进代码( )
void solve() { int n; std::cin >> n; std::vector<int> v(n); std::vector<int> left(n); for (int i = 0; i < n; i++) { std::cin >> v[i]; } for (int i = 1; i < n; i++) { left[i] = (v[i] == v[i - 1]) ? left[i - 1] : i; } int q, l, r; std::cin >> q; for (int i = 0; i < q; i++) { std::cin >> l >> r; --l, --r; if (left[l] != left[r]) { std::cout << left[r] << ' ' << left[r] + 1 << '\n'; } else { std::cout << "-1 -1\n"; } } }
之后再补 E-G 题。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析