AtCoder Beginner Contest 338
A - Capitalized? (abc338 A)
题目大意
给定一个字符串,问是否满足下述条件:
- 第一个字母大写
- 其余字母小写
解题思路
逐位判断即可。也可以将字符串变成上述形式,然后判断与原串是否相等。
神奇的代码
#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; auto t = s; t[0] = toupper(t[0]); transform(t.begin() + 1, t.end(), t.begin() + 1, ::tolower); if (s == t) cout << "Yes" << endl; else cout << "No" << endl; return 0; }
B - Frequency (abc338 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); array<int, 26> cnt{}; string s; cin >> s; for (auto& i : s) { cnt[i - 'a']++; } int maxx = ranges::max(cnt); for (int i = 0; i < 26; ++i) if (cnt[i] == maxx) { cout << char(i + 'a') << '\n'; break; } return 0; }
C - Leftover Recipes (abc338 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<LL> q(n), a(n), b(n); for (auto& x : q) cin >> x; for (auto& x : a) cin >> x; for (auto& x : b) cin >> x; LL ans = 0; for (int i = 0; i <= 1000000; ++i) { vector<LL> left(n); for (int j = 0; j < n; ++j) { left[j] = q[j] - 1ll * i * a[j]; } if (ranges::min(left) < 0) { continue; } LL r = 1e9; for (int j = 0; j < n; ++j) { if (b[j]) r = min(r, left[j] / b[j]); } ans = max(ans, r + i); } cout << ans << '\n'; return 0; }
D - Island Tour (abc338 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<int> x(m); for (auto& i : x) { cin >> i; --i; } vector<array<int, 3>> edge(m - 1); for (int i = 0; i < m - 1; ++i) { int u = x[i], v = x[i + 1]; if (u > v) swap(u, v); edge[i] = {u, v, v - u}; } LL sum = 0; for (auto& e : edge) { sum += e[2]; } LL ans = sum; vector<vector<int>> l(n), r(n); for (int i = 0; i < m - 1; ++i) { l[edge[i][0]].push_back(i); r[edge[i][1]].push_back(i); } for (int i = 0; i < n; ++i) { for (auto id : l[i]) { sum -= edge[id][2]; edge[id][2] = n - edge[id][2]; sum += edge[id][2]; } for (auto id : r[i]) { sum -= edge[id][2]; edge[id][2] = n - edge[id][2]; sum += edge[id][2]; } ans = min(ans, sum); } cout << ans << '\n'; return 0; }
E - Chords (abc338 E)
题目大意
个点的圆环,给定条线段,端点不重合,问是否有交点。
解题思路
考虑枚举线段,我们需要判断是否有线段与其相交,就需要:
- 判断所有左端点在的线段的右端点的最大值是否大于。
- 判断所有右端点在的线段的左端点的最小值是否小于。
对于第一个判断,则对线段的左端点排序,那上述判断就是一个关于右端点的区间最值问题,因为是多次查询而无修改,则用维护即可。这个区间范围直接通过关于左端点对和 的二分得到。
对于第二个判断,则对线段的右端点排序,那上述判断就是一个关于左端点的区间最值问题,因为是多次查询而无修改,则用维护即可。这个区间范围直接通过关于左端点对和 的二分得到。
时间复杂度为
好像有更简洁的做法
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; template <typename T, class F = function<T(const T&, const T&)>> class SparseTable { public: int n; vector<vector<T>> mat; F func; SparseTable(const vector<T>& a, const F& f) : func(f) { n = static_cast<int>(a.size()); int max_log = 32 - __builtin_clz(n); mat.resize(max_log); mat[0] = a; for (int j = 1; j < max_log; j++) { mat[j].resize(n - (1 << j) + 1); for (int i = 0; i <= n - (1 << j); i++) { mat[j][i] = func(mat[j - 1][i], mat[j - 1][i + (1 << (j - 1))]); } } } T get(int from, int to) const { // [from, to] assert(0 <= from && from <= to && to <= n - 1); int lg = 32 - __builtin_clz(to - from + 1) - 1; return func(mat[lg][from], mat[lg][to - (1 << lg) + 1]); } }; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n; cin >> n; vector<array<int, 2>> e(n); for (auto& i : e) { cin >> i[0] >> i[1]; if (i[0] > i[1]) swap(i[0], i[1]); } sort(e.begin(), e.end(), [](const array<int, 2>& a, const array<int, 2>& b) { return a[0] < b[0]; }); vector<int> s1(n), t1(n); for (int i = 0; i < n; ++i) { s1[i] = e[i][0]; t1[i] = e[i][1]; } SparseTable<int> rb(t1, [&](int a, int b) { return max(a, b); }); auto ee = e; sort(ee.begin(), ee.end(), [](const array<int, 2>& a, const array<int, 2>& b) { return a[1] < b[1]; }); vector<int> s2(n), t2(n); for (int i = 0; i < n; ++i) { s2[i] = ee[i][1]; t2[i] = ee[i][0]; } SparseTable<int> ls(t2, [&](int a, int b) { return min(a, b); }); bool ok = false; auto check = [&](int l, int r, auto& pos, auto& st) { auto L = ranges::upper_bound(pos, l) - pos.begin(); auto R = ranges::lower_bound(pos, r) - pos.begin(); if (L >= R) return false; auto v = st.get(L, R - 1); return v < l || v > r; }; for (auto& i : e) { if (check(i[0], i[1], s1, rb) || check(i[0], i[1], s2, ls)) { ok = true; break; } } if (ok) cout << "Yes" << endl; else cout << "No" << endl; return 0; }
F - Negative Traveling Salesman (abc338 F)
题目大意
给定一张图,边有边权,问访问所有点的最小边权和。边权有负,但无负环。多次访问算多次边权。
解题思路
一个朴素的做法就是状压,设 表示当前访问的点的状态为,当前在点的最小距离。
因为会有边权为负,直接的话(即两个循环,和 ),在转移时不变的情况下,可能会得到更优的 for,导致 原来往后更新的状态不是最优的。
这跟用求最短距离遇到的问题一样,需仿照那样,用一个优先队列来维护 顺序,从最小的状态 往后转移,或者像那样,通过多次进队解决,由于只有 ,跑一次 也行。
考虑其复杂度,状态数已经是 是的级别了,再加上 优先队列的 复杂度,第一次尝试时不出意料超时了。
考虑优化,注意到原来的两层循环状态,可能转移到 或 ,其中第二维大小关系不是固定的,但第一维要么更大,要么不变,而我们使用 式的方式维护更新时,主要是防止不变的转移造成的重复更新。
原来是直接维护状态的转移顺序,状态数达到 ,但转移的第一维有明显的方向性,因此我们可以对第一维一层一层的求解,在每一层的求解使用优先队列维护转移。这样优先队列里的状态数降为,只有 就基本没什么耗时,最后的复杂度就是。
神奇的代码
#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<array<int, 2>>> edge(n); for (int i = 0; i < m; ++i) { int u, v, w; cin >> u >> v >> w; --u, --v; edge[u].push_back({v, w}); } int up = (1 << n); vector<vector<int>> dp(up, vector<int>(n, INT_MAX)); for (int i = 0; i < n; ++i) { dp[1 << i][i] = 0; } for (int i = 0; i < up; ++i) { priority_queue<array<int, 3>, vector<array<int, 3>>, greater<array<int, 3>>> pq; for (int j = 0; j < n; ++j) { if (dp[i][j] != INT_MAX) pq.push({dp[i][j], i, j}); } while (!pq.empty()) { auto [d, s, u] = pq.top(); pq.pop(); if (dp[s][u] < d) continue; for (auto [v, w] : edge[u]) { if (dp[s | (1 << v)][v] > dp[s][u] + w) { dp[s | (1 << v)][v] = dp[s][u] + w; if ((s | (1 << v)) == s) pq.push({dp[s | (1 << v)][v], s | (1 << v), v}); } } } } int ans = INT_MAX; for (int i = 0; i < n; ++i) { ans = min(ans, dp[(1 << n) - 1][i]); } if (ans == INT_MAX) { cout << "No" << '\n'; } else cout << ans << '\n'; return 0; }
G - evall (abc338 G)
题目大意
给定一个字符串,包含数字和。问所有的子串的表达式的值。若表达式不合法则其值为。
解题思路
<++>
神奇的代码
本文作者:~Lanly~
本文链接:https://www.cnblogs.com/Lanly/p/17992290
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
2022-01-27 Atcoder Beginner Contest 236 G Good Vertices
2022-01-27 Atcoder Beginner Contest 236 F Spices
2022-01-27 Atcoder Beginner Contest 236 E Average and Median