AtCoder Beginner Contest 350
A - Past ABCs (abc350 A)
题目大意
给定一个形如 ABCXXX
的字符串。
问XXX
是否是之间,且不能是 。
解题思路
将后三位转换成数字后判断即可。
神奇的代码
a = int(input().strip()[3:]) if a >= 1 and a <= 349 and a != 316: print("Yes") else: print("No")
B - Dentist Aoki (abc350 B)
题目大意
给定个 序列。
进行次操作,每次操作反转某一位上的 。
问最后 的个数。
解题思路
反转操作的复杂度是,因此直接模拟反转即可,最后求和得到答案。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n, q; cin >> n >> q; vector<int> a(n, 1); while (q--) { int x; cin >> x; --x; a[x] ^= 1; } int ans = accumulate(a.begin(), a.end(), 0); cout << ans << '\n'; return 0; }
C - Sort (abc350 C)
题目大意
给定一个的排序,通过最多次操作以下操作将其变得有序。
操作为,交换任意两个数。
输出任意可行的操作次数及其对应的操作步骤。
解题思路
从,依次考虑将 交换到第 位。经过 次操作后则必定有序。
因此需要记录 表示数字 所在的位置,每次交换第 位,就将交换到第 位,重复执行次即可。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n; cin >> n; vector<int> pos(n); vector<int> a(n); for (int i = 0; i < n; i++) { int x; cin >> x; --x; pos[x] = i; a[i] = x; } vector<array<int, 2>> ans; for (int i = 0; i < n; i++) { if (a[i] == i) continue; ans.push_back({i, pos[i]}); swap(a[i], a[pos[i]]); swap(pos[a[i]], pos[a[pos[i]]]); } cout << ans.size() << '\n'; for (auto& p : ans) { cout << p[0] + 1 << ' ' << p[1] + 1 << '\n'; } return 0; }
D - New Friends (abc350 D)
题目大意
给定一张无向图,若三点,存在:
- 有连边
- 有连边
- 无连边
则连边。
问最多能连多少次边。
解题思路
考虑最简单的一条链的情况,容易发现可以连的边有
- =>
- =>
- =>
观察的新增边的情况,会发现它可以和所有能到达的点连边,即最终情况下,一个连通块内的任意两点都会连边,即变成一张完全图。
因此得到每个连通块的点数 和边数 ,最终情况下该连通块会有 条边,而已经有条(无向)边,因此可以连 条边。
所有连通块的连边次数相加即为答案。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n, m; cin >> n >> m; vector<vector<int>> edge(n); for (int i = 0; i < m; i++) { int u, v; cin >> u >> v; --u, --v; edge[u].push_back(v); edge[v].push_back(u); } LL ans = 0; vector<int> vis(n, 0); for (int i = 0; i < n; ++i) { if (vis[i]) continue; queue<int> team; team.push(i); vis[i] = 1; int cp = 1, ce = 0; while (!team.empty()) { int u = team.front(); team.pop(); for (auto v : edge[u]) { ++ce; if (vis[v]) continue; vis[v] = 1; team.push(v); cp++; } } ans += 1ll * cp * (cp - 1) - ce; } ans /= 2; cout << ans << '\n'; return 0; }
E - Toward 0 (abc350 E)
题目大意
给定一个数字,。通过两类操作,使得变为 。
- 操作一,花费代价,使得
- 操作二,花费代价,掷骰子,等概率掷出中的一个,使得
问最优情况下,最小期望花费。
解题思路
期望题,根据定义,当前的期望值是所有后继情况的期望值的概率加权。
设表示当前数字为 ,将其变为 的最小期望花费。
边界条件很明显就是 。
虽然是期望,但它问的是最优情况下的最小花费,那就是一个决策最优问题,考虑我的决策是什么。
很显然,决策就是操作一还是操作二,如果我决定执行操作一,会有一个期望值,执行操作二,会有另一个期望值,这两个期望值取最小,就是我做出的最优决策。因此需要分别求出操作一和操作二的期望花费。
根据定义,当前的期望值是所有后继情况的期望值的概率加权。
当我执行操作一后,后继情况只有一个,那就是,达到这个情况的概率是 。因此操作一的期望花费。
当我执行操作二后,后继情况有个:
到达每一个后继情况的概率都是。
根据期望定义,可以得到操作二的期望花费
但注意到那一项是 ,与左式是一样的,这会造成循环求值,这里我们将右边的 移到左边,合并同类项,就可以得到真正的
得到两个操作的期望花费后,接下来就是做决策——取花费最小的,作为 的值。这样是转移了。
虽然 有 ,但由于每次都至少,最多除以 次就变成 ,总的状态数其实很少,只有,大概就的数量级。
从上面的分析可以看出,期望和之间的区别仅仅是计算转移代价时需要用到期望定义来算,最终还是根据不同操作之间的代价取最优,还是个决策问题。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); LL n; int a, x, y; cin >> n >> a >> x >> y; map<LL, double> dp; auto dfs = [&](auto dfs, LL u) -> double { if (u == 0) return 0.; if (dp.find(u) != dp.end()) return dp[u]; double cost1 = dfs(dfs, u / a) + x; double cost2 = 0; for (int i = 2; i <= 6; i++) { cost2 += dfs(dfs, u / i); } cost2 = cost2 / 5 + y * 6. / 5; return dp[u] = min(cost1, cost2); }; dfs(dfs, n); cout << fixed << setprecision(10) << dp[n] << '\n'; return 0; }
F - Transpose (abc350 F)
题目大意
给定一个括号序列,长度为,其中也包括大小写字母。
依次处理每个匹配的括号里的字符,将其左右颠倒,并将大小写字母变换。
问最终的字符串。
解题思路
考虑朴素的做法,进行括号匹配,然后处理括号内的字符串,容易发现最坏情况下复杂度是,比如((((((((((asjigjiogjwifjwefckfj))))))))))
。
注意到同一个字符块执行两次上述变换后相当于没变换,从上述的最坏情况下可以启示我们,我们不需要实际进行变换,仅仅将这一块字符串看作整体,然后打个标记。就跟线段树的懒标记差不多。
比如(((as)(sf))(ef))
,我们先将每一块字符串看作整体,从 开始标号,则变为(((0)(1))(2))
,然后处理每对匹配的括号,比如变成了((34)(2))
,然后处理 (34)
,这里我们就不给34
重复打标记,因为那样的复杂度可能会变回原来的,而是将 看成一个整体 ,在上打标记,最后输出时再传递给 。变成 (5(2))
,然后是(56)
,然后是7
。
就跟线段树的思想一样,我们会发现,它们的关系形成了一棵树的关系(但可能不是二叉树),然后打标记都是在父亲节点打标记,最后输出时,从根节点开始遍历,不断下放标记,下放到叶子时,再根据标记决定是否倒序输出即可。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); string s; cin >> s; s = "(" + s + ")"; vector<string> info; info.push_back("("); info.push_back(")"); string t; vector<int> tr; for (auto& i : s) { if (i == '(' || i == ')') { if (!t.empty()) { tr.push_back(info.size()); info.push_back(t); } t.clear(); if (i == '(') tr.push_back(0); else tr.push_back(1); } else t += i; } vector<vector<int>> edge(info.size()); stack<int> st; for (auto i : tr) { if (i == 0) { // ( st.push(0); } else if (i == 1) { // ) int fa = info.size(); info.push_back(""); edge.push_back(vector<int>()); while (!st.empty() && st.top() != 0) { edge[fa].push_back(st.top()); st.pop(); } st.pop(); st.push(fa); } else { st.push(i); } } auto inverse = [](string& s) { reverse(s.begin(), s.end()); for (auto& i : s) { if (islower(i)) i = toupper(i); else i = tolower(i); } }; auto dfs = [&](auto dfs, int u, int d) -> void { if (edge[u].empty()) { // leaf if (d) inverse(info[u]); cout << info[u]; return; } if (d) reverse(edge[u].begin(), edge[u].end()); for (auto v : edge[u]) { dfs(dfs, v, d ^ 1); } }; dfs(dfs, info.size() - 1, 1); return 0; }
G - Mediator (abc350 G)
题目大意
给定个点,执行下列 次操作,分两种,强制在线。
1 u v
,连无向边,连边之前保证不连通。2 u v
,询问是否有一个点,使得。
解题思路
<++>
神奇的代码
本文作者:~Lanly~
本文链接:https://www.cnblogs.com/Lanly/p/18148320
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?