Codeforces Round 921 (Div. 1) 记录(A-D)
比赛链接:https://codeforces.com/contest/1924
官解链接:https://codeforces.com/blog/entry/125137
这场整体来说表现还可以,最终 performance
CF1924A. Did We Get Everything Covered?
不错的贪心题。进入状态慢了,写了几个小错误调到
题意
给定字符集大小
解法
关键结论:
答案为 NO 时,不符合条件的串的构造需要小心。给出一个显然正确的构造:每次更新长度时,记录最后一次遇到的字母,添加到答案末尾。之后向结尾不断增加(当前段)未出现的字母直至长度为
代码实现
void solve() { int n, k, m; cin >> n >> k >> m; string s; cin >> s; int l = 0, vis = 0; string ans; for (char c : s) { vis |= 1 << (c - 'a'); if (vis == (1 << k) - 1) { ans += c; l++, vis = 0; } } if (l >= n) { cout << "YES\n"; } else { cout << "NO\n"; int b = countr_one((unsigned)vis); while ((int)ans.size() < n) ans += char('a' + b); cout << ans << "\n"; }
CF1924B. Space Harbour
一开始想写线段树,结果答案错了还发现自己不会写了……遂更换做法,然后又被珂朵莉树细节坑了,浪费很多时间还 dirt
题意
有
每个点的代价为其左侧最近太空港的权值乘以它到右侧最近太空港的距离。特别地,若一个点已经有太空港,代价为
-
在某个(之前没有太空港的)点增加一个太空港。
-
查询区间
中点的代价总和。
题解
考虑两个相邻的位置为
实时维护这些线段(可以使用 set
),而对于代价和的区间查询,直接使用区间加等差数列(一次函数)的线段树完成,就可以快速完成这题(这也是 jiangly 的做法)。当然区间加等差数列、区间求和应该也可以纯树状数组完成维护,但我不会写,也不建议这么写。
然而不巧的是,我在写线段树时发现自己忘记怎么手写这样的线段树了,而又没有临时切换到 ACL 等模板。于是我切换到了一种类似分块的方法:对完全包括的线段,使用树状数组直接求和。而两端的可能不完全包含的线段,拿出来使用等差数列求和公式单独处理即可。另外注意两端可能在同一个线段内。
两种解法的时间复杂度都是
参考代码
树状数组:提交
CF1924C. Fractal Origami
很简单的题,手动折一下基本有思路了,再加点高中数学实现。
题意
有一张边长为
题解
只要分别算出
考虑每次折叠对
-
折叠后正方形的边长为
。 -
展开一次,我们可以看到四个谷,它们的总长度是(折叠后)边长的四倍。
-
我们还要再展开
次。由对称性,每次展开后原来的峰或谷都会变成相同长度的峰和谷,即总长度变为原来的两倍。它们的总长度是 ,峰和谷各占一半。 -
因此我们可以下结论,第
次折叠新生成的峰和谷的长度均为 。
因此
代码实现
void solve() { int n; cin >> n; Z ma = 0, mb = 0, va = 0, vb = 2; n--; int na = (n + 1) / 2, nb = n / 2; Z a = 2 * (Z(2).pow(na) - 1), b = 2 * (Z(2).pow(nb) - 1); ma += a, va += a; mb += b, vb += b; Z ans = (ma * vb - mb * va) / (2 * vb * vb - va * va); cout << ans << "\n"; }
CF1924D. Balanced Subsequences
虽然 C 很简单也出的很快,但只剩二十多分钟,所以直接就没有仔细看 D。
题意
求有
题解
记
首先翻译一下官解。
- 开头为右括号的串显然数量为
; - 开头为左括号的串数量为
,即“原串最长匹配对数为 ”与“删去最左侧左括号后最长匹配对数为 ”等价。这部分不是那么显然,考虑一种求最长匹配子序列的贪心方法:每当遇到右括号,就将其与最近未匹配的左括号匹配。由于 ,最终留下未匹配的子序列一定是一些右括号紧跟一些左括号。则第一个左括号一定被匹配,且删去它,剩余串的匹配对数一定减去 。反证:否则(若不减),(由于右括号的数量不变)一定仍存在未匹配的右括号,重新加回最左侧的左括号会导致匹配对数增加。
则有递归式
但这个数学归纳的过程并不是很直观。下面介绍更容易理解的折线法,这个做法是折线法求卡塔兰数的推广。
考虑在
我们在第一个交点处,将之后的折线关于直线翻转,则终点变为
代码实现
Z get(int n, int m, int k) { if (k >= min(n, m)) return comb.get(n + m, m); else return comb.get(n + m, k); } void solve() { int n, m, k; cin >> n >> m >> k; cout << get(n, m, k) - get(n, m, k - 1) << "\n"; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具