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)
题目大意
给定\(5n\)个分数,去掉最高的 \(n\) 个和最低的\(n\)个,然后算剩下数的平均分。
解题思路
排序后选中间\(3n\)个数的平均值即可。
神奇的代码
#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)
题目大意
当前位置为\((0,0)\),给定一个 LRUD
操作序列,问在执行该序列时,所访问的点坐标是否重复。
解题思路
拿\(set\)记录下访问过的下标,加速查询,模拟即可。
神奇的代码
#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)
题目大意
给定\(n\)张卡,每张卡正反两面都写了个数。
现在可以将一些卡面正反颠倒,问有多少种策略,使得前一张的反面和后一张卡的正面的数都不同。
解题思路
从搜索状态考虑,为了策略的合法性(前后两张卡反正面数不同)需要的状态为前一张卡是否翻转,其余信息都可以不需要。
因此设\(dp[i][0/1]\)表示前 \(i\)张卡,第 \(i\)张卡是没翻转
/翻转
,且前面卡面都符合题意要求的方案数。
转移就枚举第 \(i-1\)位的翻转情况,根据是否符合题意要求。
因为每次仅涉及 \(i-1\)的状态,第一维可以滚动数组优化。
神奇的代码
#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)
题目大意
现在有一个\(1 \sim n\)排列。已知 \(m\)个大小关系,即第 \(a_i\)个数小于第 \(b_i\)个数。
问能否从这些关系中确定唯一的排列,可以则输出排列,不可以则输出 No
。
解题思路
初看该题时没啥思路,然后想着看看一些特别情况,比如如何确定\(1\)的位置。
一个位置是 \(1\),那就是说该位置小于其他所有位置。而小于关系
是有传递性的,于是考虑如果把这个传递性体现出来,来求得一个位置小于所有位置。
这就可以考虑一张由小于关系
构成的有向图,\(a < b\)则第 \(a\)号点连一条边到第 \(b\)号点。
如果我能一个入度为 \(0\)的点出发,能到达 \(n-1\)个点 ,那就意味着这个点小于其他所有点,此点就是\(1\)。当然如果入度为 \(0\)的点有多个,那么此时 这些点都可能是\(1\),那就不行了。
而 \(1\)考虑完后,就去掉其影响,考虑 \(2\),发现还是原来的子问题。
然后就发现就是一个拓扑排序,在这过程中,队列里(此时度数为 \(0\)的点)只能有一个(不然有多个点都可以取该值), 依次确定出\(1,2,3\)的位置。
神奇的代码
#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)
题目大意
给定\(n\)号点,依次排列,每个点都有一个数组 \(s\),第 \(i\)个点的 \(s_{i,j}\)表示从\(i\)号点能否到达 \(i+j\)号点。
对于每个\(k=2,3,4,...,n-1\),问从 \(1\)号点出发,再不经过 \(k\)号点的情况下,到达第 \(n\)号点的最小距离。
解题思路
注意这题的\(s\)的长度\(m\)只有 \(10\)。
如果不考虑\(k\)的话,就设 \(dp[i]\)表示到达 \(i\)号点的最小距离,因为只能往标号大的点走,因此转移就枚举下一个到达的点,取最小值即可。
但有\(k\)的话, \(dp[n]\)就不一定合法了,因为在转移的时候我们没有限制不能走第 \(k\)号点,所以不知最终的 \(dp[n]\)是否经过第 \(k\)号点。
但因为关键决策就是第 \(k\)号点,因此我们揪出转移会涉及到 \(k\)号点的点,只有\(m - 1\)个,即第 \(k - m + 1, k - m + 2,...,k - 1\)号点,对于这些点 \(l\),我们枚举它们下一个到达的,且大于\(k\)的点 \(r\),此时从\(1\)号点到 \(n\)号点分成了三段:
- \(1 \to l\)
- \(l \to r\)
- \(r \to n\)
那此时从 \(1\)号点到 \(l\)号点, \(r\)号点到 \(n\)号点都是没有限制的,而这个可以事先预处理\(dis1[l]\)表示 \(1\)号点到 \(l\)号点的最短距离,以及 \(dis2[r]\)表示从 \(r\)号点到 \(n\)号点的最短距离。而\(l\)号点到 \(r\)号点的距离是 \(1\),且保证了不经过 \(k\)号点,因此总的距离是 \(dis1[l] + dis2[r] + 1\)。
预处理 \(dis1[i],dis2[i]\)的时间复杂度是 \(O(nm)\),对于每个 \(k\)的时间复杂度是\(O(m^2)\)(有 \(m\)个枚举点,每个点枚举下一个目的地有 \(m\)个),因此总的时间复杂度是 \(O(nm^2)\)
这其实跟一张图,必定经过某条边的最短路是一样的想法。
神奇的代码
#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)
题目大意
两个长度为\(n\)的数组 \(A\)和 \(B\)。
现在可以对 数组\(A\)进行左循环移位操作,即将 \(A\)的第一个元素放到最后。
问 \(\sum_{i=0}^{n - 1} A_i | B_i\)的最大值。
解题思路
朴素的方法复杂度是\(O(n^2)\),即有 \(O(n)\)种操作情况,每种情况的答案计算复杂度为 \(O(n)\)。考虑优化计算。
因为位运算各个数位独立,我们依次考虑每个数位\(1\)的数量,再乘以该数位的基
(就是\(2^i\))就可以得到结果。
即\(\sum_{i = 0}^{\log m}2^i \sum_{j=0}^{n - 1}A^{i}_{(j + k) \% n} | B^{i}_{j}\)
其中\(k\)就是进行了 \(k\)次操作。\(A^i_j\)就是 \(A_j\)在二进制下的第 \(i\)位的值,\(0\)或者 \(1\)。
单看第二个求和式子感觉是个卷积式,只要把\(B\)颠倒一下,就是\(\sum_{j=0}^{n - 1}A^{i}_{(j + k) \% n} | B^{i}_{n - j - 1}\)
而因为 \(A | B = A + B - A \& B\),因此\(\sum_{j=0}^{n - 1}A^{i}_{(j + k) \% n} + B^{i}_{n - j - 1} - A^{i}_{(j + k) \% n} \& B^{i}_{n - j - 1}\)
前两项是定值,而后一项,因为其取值只有\(0\)和 \(1\), 与运算
和乘法运算
是一样的结果,因此其可以看成是个卷积。
设\(a = (A_0, A_1, ... , A_{n-1}, A_0, A_1, ..., A_{n - 1}), b = (B_{n-1}, B_{n-2}, ..., B_{1}, B_{0}), c = a * b\)
此时\(c_{n + k - 1} = \sum_{i = 0}^{n - 1} b_{i} \times a_{n + k - 1 - i}\),和上面的\(\&\)卷积式是一样的。
因此通过一次卷积,就能得到每个数位
的移动\(k\)次后的 或运算
的结果,每种操作的每个数位累计求和,求个最大值即可。卷积复杂度是\(O(n \log n)\),总的时间复杂度是 \(O(n \log n \log m)\)
神奇的代码
Ex - Balanced Tree (abc291 h)
题目大意
<++>
解题思路
<++>
神奇的代码