AtCoder Beginner Contest 350
A - Past ABCs (abc350 A)
题目大意
给定一个形如 ABCXXX
的字符串。
问XXX
是否是001→349之间,且不能是 316。
解题思路
将后三位转换成数字后判断即可。
神奇的代码
a = int(input().strip()[3:]) if a >= 1 and a <= 349 and a != 316: print("Yes") else: print("No")
B - Dentist Aoki (abc350 B)
题目大意
给定n个 01序列。
进行q次操作,每次操作反转某一位上的 01。
问最后 1的个数。
解题思路
反转操作的复杂度是O(1),因此直接模拟反转即可,最后求和得到答案。
神奇的代码
#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)
题目大意
给定一个1→n的排序,通过最多n−1次操作以下操作将其变得有序。
操作为,交换任意两个数。
输出任意可行的操作次数及其对应的操作步骤。
解题思路
从i=1→n,依次考虑将 i交换到第 i位。经过 n−1次操作后则必定有序。
因此需要记录 pos[i]表示数字 i所在的位置,每次交换第i,pos[i] 位,就将i交换到第 i位,重复执行n−1次即可。
神奇的代码
#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)
题目大意
给定一张无向图,若三点x,y,z,存在:
- x,y有连边
- y,z有连边
- x,z无连边
则连边x,z。
问最多能连多少次边。
解题思路
考虑最简单的一条链的情况1→2→3→4,容易发现可以连的边有
- 1→2,2→3 => 1→3
- 1→3,3→4 => 1→4
- 2→3,3→4 => 2→4
观察1的新增边的情况,会发现它可以和所有能到达的点连边,即最终情况下,一个连通块内的任意两点都会连边,即变成一张完全图。
因此BFS得到每个连通块的点数 cp和边数 ce,最终情况下该连通块会有 cp∗(cp−1)2 条边,而已经有ce条(无向)边,因此可以连 cp∗(cp−1)2−ce条边。
所有连通块的连边次数相加即为答案。
神奇的代码
#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)
题目大意
给定一个数字n,x,y,a。通过两类操作,使得n变为 0。
- 操作一,花费代价x,使得 n=⌊na⌋
- 操作二,花费代价y,掷骰子,等概率掷出1→6中的一个b,使得 n=⌊nb⌋
问最优情况下,最小期望花费。
解题思路
期望题,根据定义,当前的期望值是所有后继情况的期望值的概率加权。
设dp[i]表示当前数字为 x,将其变为 0的最小期望花费。
边界条件很明显就是 dp[0]=0。
虽然是期望,但它问的是最优情况下的最小花费,那就是一个决策最优问题,考虑我的决策是什么。
很显然,决策就是操作一还是操作二,如果我决定执行操作一,会有一个期望值,执行操作二,会有另一个期望值,这两个期望值取最小,就是我做出的最优决策。因此需要分别求出操作一和操作二的期望花费。
根据定义,当前的期望值是所有后继情况的期望值的概率加权。
当我执行操作一后,后继情况只有一个,那就是dp[⌊na⌋],达到这个情况的概率是 1。因此操作一的期望花费cost1=dp[⌊na⌋]+x。
当我执行操作二后,后继情况有6个:
- dp[⌊n1⌋]
- dp[⌊n2⌋]
- dp[⌊n3⌋]
- dp[⌊n4⌋]
- dp[⌊n5⌋]
- dp[⌊n6⌋]
到达每一个后继情况的概率都是16。
根据期望定义,可以得到操作二的期望花费 dp[n]=∑6i=1dp[⌊ni⌋]6+y
但注意到i=1那一项是 dp[n],与左式是一样的,这会造成循环求值,这里我们将右边的 dp[n] 移到左边,合并同类项,就可以得到真正的cost2=∑6i=2dp[⌊ni⌋]5+65y
得到两个操作的期望花费cost1,cost2后,接下来就是做决策——取花费最小的,作为 dp[n]的值。这样是转移了。
虽然 n有 O(1018),但由于每次都至少/2,最多除以 log次就变成 0,总的状态数其实很少,只有O(log2∗log3∗log4∗log5∗log6),大概就4e7的数量级。
从上面的分析可以看出,期望dp和dp之间的区别仅仅是计算转移代价时需要用到期望定义来算,最终还是根据不同操作之间的代价取最优,还是个决策问题。
神奇的代码
#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)
题目大意
给定一个括号序列s,长度为n,其中也包括大小写字母。
依次处理每个匹配的括号里的字符,将其左右颠倒,并将大小写字母变换。
问最终的字符串。
解题思路
考虑朴素的做法,进行括号匹配,然后处理括号内的字符串,容易发现最坏情况下复杂度是O(n2),比如((((((((((asjigjiogjwifjwefckfj))))))))))
。
注意到同一个字符块执行两次上述变换后相当于没变换,从上述的最坏情况下可以启示我们,我们不需要实际进行变换,仅仅将这一块字符串看作整体,然后打个标记。就跟线段树的懒标记差不多。
比如(((as)(sf))(ef))
,我们先将每一块字符串看作整体,从 0开始标号,则变为(((0)(1))(2))
,然后处理每对匹配的括号,比如变成了((34)(2))
,然后处理 (34)
,这里我们就不给34
重复打标记,因为那样的复杂度可能会变回原来的O(n2),而是将 34看成一个整体 5,在5上打标记,最后输出时再传递给 34。变成 (5(2))
,然后是(56)
,然后是7
。
就跟线段树的思想一样,我们会发现34→5,2→6,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)
题目大意
给定n个点,执行下列 q次操作,分两种,强制在线。
1 u v
,连无向边u→v,连边之前保证u,v不连通。2 u v
,询问是否有一个点x,使得u→x→v。
解题思路
<++>
神奇的代码
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 为DeepSeek添加本地知识库
· 精选4款基于.NET开源、功能强大的通讯调试工具
· DeepSeek智能编程
· 大模型工具KTransformer的安装
· [计算机/硬件/GPU] 显卡