AtCoder Beginner Contest 291
A - camel Case (abc291 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; int pos = find_if(s.begin(), s.end(), [](char c){ return isupper(c); }) - s.begin(); cout << pos + 1 << '\n'; return 0; }
B - Trimmed Mean (abc291 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; cin >> n; vector<int> s(n * 5); for(auto &i : s) cin >> i; sort(s.begin(), s.end()); int sum = accumulate(s.begin() + n, s.end() - n, 0); cout << fixed << setprecision(10) << 1.0 * sum / (3 * n) << '\n'; return 0; }
C - LRUD Instructions 2 (abc291 c)
题目大意
当前位置为,给定一个 LRUD
操作序列,问在执行该序列时,所访问的点坐标是否重复。
解题思路
拿记录下访问过的下标,加速查询,模拟即可。
神奇的代码
#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 x = 0, y = 0; int n; string s; cin >> n >> s; auto check = [&](){ set<pair<int, int>> visit; visit.insert({x, y}); for(auto &i : s){ if (i == 'L') -- x; else if (i == 'R') ++ x; else if (i == 'D') -- y; else if (i == 'U') ++ y; else assert(0); if (visit.find({x, y}) != visit.end()) return true; visit.insert({x, y}); } return false; }; cout << (check() ? "Yes" : "No") << '\n'; return 0; }
D - Flip Cards (abc291 d)
题目大意
给定张卡,每张卡正反两面都写了个数。
现在可以将一些卡面正反颠倒,问有多少种策略,使得前一张的反面和后一张卡的正面的数都不同。
解题思路
从搜索状态考虑,为了策略的合法性(前后两张卡反正面数不同)需要的状态为前一张卡是否翻转,其余信息都可以不需要。
因此设表示前 张卡,第 张卡是没翻转
/翻转
,且前面卡面都符合题意要求的方案数。
转移就枚举第 位的翻转情况,根据是否符合题意要求。
因为每次仅涉及 的状态,第一维可以滚动数组优化。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; const int mo = 998244353; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n; cin >> n; vector<pair<int, int>> card(n); for(auto &i : card) cin >> i.first >> i.second; vector<int> dp(2, 1); for(int i = 1; i < n; ++ i){ vector<int> tmp(2, 0); tmp[0] = dp[0] * (card[i].first != card[i - 1].first) + dp[1] * (card[i].first != card[i - 1].second); if (tmp[0] >= mo) tmp[0] -= mo; tmp[1] = dp[0] * (card[i].second != card[i - 1].first) + dp[1] * (card[i].second != card[i - 1].second); if (tmp[1] >= mo) tmp[1] -= mo; dp.swap(tmp); } cout << (dp[0] + dp[1]) % mo << '\n'; return 0; }
E - Find Permutation (abc291 e)
题目大意
现在有一个排列。已知 个大小关系,即第 个数小于第 个数。
问能否从这些关系中确定唯一的排列,可以则输出排列,不可以则输出 No
。
解题思路
初看该题时没啥思路,然后想着看看一些特别情况,比如如何确定的位置。
一个位置是 ,那就是说该位置小于其他所有位置。而小于关系
是有传递性的,于是考虑如果把这个传递性体现出来,来求得一个位置小于所有位置。
这就可以考虑一张由小于关系
构成的有向图,则第 号点连一条边到第 号点。
如果我能一个入度为 的点出发,能到达 个点 ,那就意味着这个点小于其他所有点,此点就是。当然如果入度为 的点有多个,那么此时 这些点都可能是,那就不行了。
而 考虑完后,就去掉其影响,考虑 ,发现还是原来的子问题。
然后就发现就是一个拓扑排序,在这过程中,队列里(此时度数为 的点)只能有一个(不然有多个点都可以取该值), 依次确定出的位置。
神奇的代码
#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> du(n); 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); ++ du[v]; } vector<int> ans(n); auto check = [&](){ queue<int> team; for(int i = 0; i < n; ++ i){ if (du[i] == 0) team.push(i); } int cur = 0; while(!team.empty()){ if (team.size() != 1) return false; int u = team.front(); ++ cur; ans[u] = cur; team.pop(); for(auto &v : edge[u]){ du[v] --; if (du[v] == 0) team.push(v); } } return (cur == n); }; if (check()){ cout << "Yes" << '\n'; for(auto &i : ans) cout << i << ' '; }else { cout << "No" << '\n'; } return 0; }
F - Teleporter and Closed off (abc291 f)
题目大意
给定号点,依次排列,每个点都有一个数组 ,第 个点的 表示从号点能否到达 号点。
对于每个,问从 号点出发,再不经过 号点的情况下,到达第 号点的最小距离。
解题思路
注意这题的的长度只有 。
如果不考虑的话,就设 表示到达 号点的最小距离,因为只能往标号大的点走,因此转移就枚举下一个到达的点,取最小值即可。
但有的话, 就不一定合法了,因为在转移的时候我们没有限制不能走第 号点,所以不知最终的 是否经过第 号点。
但因为关键决策就是第 号点,因此我们揪出转移会涉及到 号点的点,只有个,即第 号点,对于这些点 ,我们枚举它们下一个到达的,且大于的点 ,此时从号点到 号点分成了三段:
那此时从 号点到 号点, 号点到 号点都是没有限制的,而这个可以事先预处理表示 号点到 号点的最短距离,以及 表示从 号点到 号点的最短距离。而号点到 号点的距离是 ,且保证了不经过 号点,因此总的距离是 。
预处理 的时间复杂度是 ,对于每个 的时间复杂度是(有 个枚举点,每个点枚举下一个目的地有 个),因此总的时间复杂度是
这其实跟一张图,必定经过某条边的最短路是一样的想法。
神奇的代码
#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<string> s(n); for(auto &i : s) cin >> i; vector<int> dis1(n, n + 5), dis2(n, n + 5); dis1[0] = 0; for(int i = 0; i < n - 1; ++ i){ for(int j = 0; j < m && i + j + 1 < n; ++ j){ if (s[i][j] == '1') dis1[i + j + 1] = min(dis1[i + j + 1], dis1[i] + 1); } } dis2[n - 1] = 0; for(int i = n - 2; i >= 0; -- i){ for(int j = 0;j < m && i + j + 1 < n; ++ j){ if (s[i][j] == '1') dis2[i] = min(dis2[i], dis2[i + j + 1] + 1); } } for(int i = 1; i < n - 1; ++ i){ int ans = n + 5; for(int j = max(0, i - m + 1); j < i; ++ j){ for(int k = i - j; k < m; ++ k){ if (s[j][k] == '1') ans = min(ans, dis1[j] + 1 + dis2[j + k + 1]); } } if (ans == n + 5) ans = -1; cout << ans << " \n"[i == n - 2]; } return 0; }
G - OR Sum (abc291 g)
题目大意
两个长度为的数组 和 。
现在可以对 数组进行左循环移位操作,即将 的第一个元素放到最后。
问 的最大值。
解题思路
朴素的方法复杂度是,即有 种操作情况,每种情况的答案计算复杂度为 。考虑优化计算。
因为位运算各个数位独立,我们依次考虑每个数位的数量,再乘以该数位的基
(就是)就可以得到结果。
即
其中就是进行了 次操作。就是 在二进制下的第 位的值,或者 。
单看第二个求和式子感觉是个卷积式,只要把颠倒一下,就是
而因为 ,因此
前两项是定值,而后一项,因为其取值只有和 , 与运算
和乘法运算
是一样的结果,因此其可以看成是个卷积。
设
此时,和上面的卷积式是一样的。
因此通过一次卷积,就能得到每个数位
的移动次后的 或运算
的结果,每种操作的每个数位累计求和,求个最大值即可。卷积复杂度是,总的时间复杂度是
神奇的代码
Ex - Balanced Tree (abc291 h)
题目大意
<++>
解题思路
<++>
神奇的代码
本文作者:~Lanly~
本文链接:https://www.cnblogs.com/Lanly/p/17159767.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步