AtCoder Beginner Contest 377
上周六咕咕咕了
省流版
- A. 排序判断即可
- B. 枚举判断即可
- C. 记录覆盖位置去重,总数-覆盖数即可
- D. 枚举右端点,考虑符合条件的左端点数量即可
- E. 考虑排列的图,考虑操作数与走的边数关系,利用环循环节算偏移量即可
- F. 考虑每个皇后实际覆盖的位置,枚举先前皇后计算覆盖交集去重,累加实际覆盖数即可
- G. 将操作转换成
Tries
树的节点移动,即一条链上的到达最近叶子的距离,取最小值即可
A - Rearranging ABC (abc377 A)
题目大意
给定三个字母,问能否组成ABC
。
解题思路
排个序看是否是ABC
即可。
神奇的代码
#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; sort(s.begin(), s.end()); if (s == "ABC") cout << "Yes" << '\n'; else cout << "No" << '\n'; return 0; }
B - Avoid Rook Attack (abc377 B)
题目大意
国际象棋,车,上下左右任意走,
的棋盘,给定 个车的位置。
问有多少位置,不会被车的范围覆盖。
解题思路
一个车就覆盖一行和一列。
因此就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); set<int> r, c; for (int i = 0; i < 8; ++i) { string s; cin >> s; for (int j = 0; j < 8; ++j) if (s[j] == '#') { r.insert(i); c.insert(j); } } int ans = (8 - r.size()) * (8 - c.size()); cout << ans << '\n'; return 0; }
C - Avoid Knight Attack (abc377 C)
题目大意
国际象棋,马,八个方向的日
字走法。
的棋盘,给定 个马的位置。
问有多少位置,不会被马的范围覆盖。
解题思路
因为一个🐎只覆盖八个位置,用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 n, m; cin >> n >> m; set<array<int, 2>> forbid; array<int, 8> dx = {2, 1, -1, -2, -2, -1, 1, 2}; array<int, 8> dy = {1, 2, 2, 1, -1, -2, -2, -1}; for (int i = 0; i < m; i++) { int x, y; cin >> x >> y; forbid.insert({x, y}); for (int j = 0; j < 8; j++) { int nx = x + dx[j]; int ny = y + dy[j]; if (nx >= 1 && nx <= n && ny >= 1 && ny <= n) { forbid.insert({nx, ny}); } } } LL ans = 1ll * n * n - forbid.size(); cout << ans << '\n'; return 0; }
D - Many Segments 2 (abc377 D)
题目大意
一维数轴,给定若干条线段。
问数量,其不完全包含上述中的任意线段。
解题思路
枚举,考虑有多少个 符合要求。
不完全包含任何线段,因此只需考虑 的线段,然后 即可。
即的取值范围即为(\max_{r_i \leq r} l_i, r],个数即为,对所有的求和即为答案。
对线段的右端点从小到大排序,由于是从小到大枚举的,因此可以在枚举的过程 维护。
神奇的代码
#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<array<int, 2>> seg(n); for (auto& s : seg) { cin >> s[0] >> s[1]; } sort(seg.begin(), seg.end(), [](const array<int, 2>& a, const array<int, 2>& b) { return a[1] < b[1]; }); LL ans = 0; int maxl = 0; int cur = 0; for (int i = 1; i <= m; ++i) { while (cur < n && seg[cur][1] <= i) { maxl = max(maxl, seg[cur][0]); ++cur; } ans += i - maxl; } cout << ans << '\n'; return 0; }
E - Permute K times 2 (abc377 E)
题目大意
给定一个排列,进行次操作。
每次操作,同时将所有的 替换为 。
问进行次操作后的排列。
解题思路
排列的变换,可以考虑建图,即有若干个环。
通过手试会发现,进行第一次操作,相当于走条边,进行第二次操作,相当于走条边, 第三次走条...因为本次走的点,其之前也走过了同样的步数。
即进行 次,将会走 条边,由于一个环的循环节大小为环的点数,因此其对环大小取后即为进行了 次操作后的位置,其数就是 。
因此找出每个环,假设环大小,用快速幂算出 ,即进行 次操作后的偏移量,然后求该环上所有点最终的偏移位置即可。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; long long qpower(long long a, long long b, long long mo) { long long qwq = 1; while (b) { if (b & 1) qwq = qwq * a % mo; a = a * a % mo; b >>= 1; } return qwq; } int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n; LL k; cin >> n >> k; vector<int> p(n); for (auto& i : p) { cin >> i; --i; } vector<int> vis(n); vector<int> ans(n); for (int i = 0; i < n; i++) { if (vis[i]) continue; int j = i; vector<int> cycle; while (!vis[j]) { vis[j] = 1; cycle.push_back(j); j = p[j]; } int len = cycle.size(); for (int i = 0; i < len; i++) { int step = (qpower(2, k, len) + len - 1) % len; int j = (i + step) % len; ans[cycle[i]] = cycle[j]; } } for (auto i : ans) cout << p[i] + 1 << ' '; return 0; }
F - Avoid Queen Attack (abc377 F)
题目大意
国际象棋,皇后,横竖两个对角线的任意走。
的棋盘,给定 个皇后的位置。
问有多少位置,不会被皇后的范围覆盖。
解题思路
同样考虑所有皇后覆盖了多少位置,然后用总数减去覆盖的位置。
但与🐎不同的是,一个皇后覆盖的数量位置和同级,不能像🐎一样记录所有覆盖位置。
考虑每个皇后实际覆盖的位置数,该位置数是去除了先前考虑的皇后的覆盖位置。
首先,可以算出该皇后可以覆盖的数量,即横竖两个对角线的和。然后考虑去除已经算过的覆盖的位置。
由于,因此可以直接枚举先前考虑的皇后,然后计算皇后重叠的覆盖部分,就能得到该皇后实际覆盖的位置。
所有的皇后的实际覆盖数的和,用总数减去即为答案。时间复杂度为。
想法比较朴素,实现需要一些细节。
考虑皇后的重叠部份的计算,由于覆盖的部分还会重复覆盖(即皇后重叠的部分,与皇后 也有重叠),因此计算重复覆盖时还得去重。
如果同行同列或者同对角线, 由于其数量级是,不能全部记录被覆盖的地方,可以用四个变量表示该行列或对角线是否被覆盖。
然后考虑
- 皇后的
行
与另外的列
和俩对角线
的交集,最多三个点。 - 皇后的
列
与另外的行
和俩对角线
的交集,最多三个点。 - 皇后的
俩对角线
与另外的行
和列
和一对角线
的交集,最多三个点。
遍历先前考虑的所有皇后,得到这些覆盖的点,去重后即为先前已经考虑过的覆盖的点。用该皇后可以覆盖的位置数
减去考虑过的
,即为该皇后实际覆盖的点数。
至于计算交集点,两条斜对角线的计算方式需要解一个二元一次方程组。
代码实现里,除row
外,其余的下标都是y
轴的。注意中心点被重复考虑的情况。
神奇的代码
#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<array<int, 2>> a(m); for (auto& x : a) cin >> x[0] >> x[1]; LL tot = 0; auto calc_diag = [&](int x, int y) { return min(x, n - y + 1) + min(n - x + 1, y) - 1; }; auto calc_diag2 = [&](int x, int y) { return min(x, y) + min(n - x + 1, n - y + 1) - 1; }; auto intersect = [&](int x1, int y1, int x2, int y2, int& tmp) { int d1 = x1 + y1, d2 = x2 - y2; int x = (d1 + d2), y = (d1 - d2); if ((x & 1) || (y & 1)) return false; x = x / 2; y = y / 2; tmp = y; return x >= 1 && x <= n && y >= 1 && y <= n; }; auto intersect2 = [&](int x1, int y1, int x2, int y2, int& tmp) { int d1 = x1 - y1, d2 = x2 + y2; int x = (d1 + d2), y = (d2 - d1); if ((x & 1) || (y & 1)) return false; x = x / 2; y = y / 2; tmp = y; return x >= 1 && x <= n && y >= 1 && y <= n; }; auto calc = [&](int x, int y) { LL cnt = 0; cnt += n; cnt += n; cnt += calc_diag(x, y); cnt += calc_diag2(x, y); cnt -= 3; return cnt; }; auto in_range = [&](int x) { return x >= 1 && x <= n; }; for (int i = 0; i < m; i++) { auto [x, y] = a[i]; LL forbid = calc(x, y); bool row = false, col = false, diag = false, diag2 = false; set<int> forbid_row, forbid_col, forbid_diag, forbid_diag2; for (int j = 0; j < i; ++j) { auto [x2, y2] = a[j]; if (x == x2) row = true; if (y == y2) col = true; if (x + y == x2 + y2) diag = true; if (x - y == x2 - y2) diag2 = true; forbid_row.insert(y2); if (in_range(x2 + y2 - x)) forbid_row.insert(x2 + y2 - x); if (in_range(y2 - x2 + x)) forbid_row.insert(y2 - x2 + x); forbid_col.insert(x2); if (in_range(x2 + y2 - y)) forbid_col.insert(x2 + y2 - y); if (in_range(x2 - y2 + y)) forbid_col.insert(x2 - y2 + y); int tmp; if (in_range(x + y - x2)) forbid_diag.insert(x + y - x2); if (in_range(x + y - y2)) forbid_diag.insert(y2); if (intersect(x, y, x2, y2, tmp)) forbid_diag.insert(tmp); if (in_range(y - x + x2)) forbid_diag2.insert(y - x + x2); if (in_range(x - y + y2)) forbid_diag2.insert(y2); if (intersect2(x, y, x2, y2, tmp)) forbid_diag2.insert(tmp); } int center = 0; if (row) { forbid -= n; center++; } else { forbid -= forbid_row.size(); center += forbid_row.count(y); } if (col) { forbid -= n; center++; } else { forbid -= forbid_col.size(); center += forbid_col.count(x); } if (diag) { forbid -= calc_diag(x, y); center++; } else { forbid -= forbid_diag.size(); center += forbid_diag.count(y); } if (diag2) { forbid -= calc_diag2(x, y); center++; } else { forbid -= forbid_diag2.size(); center += forbid_diag2.count(y); } tot += forbid; tot += max(center - 1, 0); } LL ans = 1ll * n * n - tot; cout << ans << '\n'; return 0; }
G - Edit to Match (abc377 G)
题目大意
给定个字符串,对于每个字符串 ,回答以下问题。
进行最少次数操作,使得 为空,或者存在,使得。
操作分两种:
- 删去末尾的字符。
- 末尾添加任意字符。
解题思路
对尾部操作,对这些字符串建立一颗Trie
树,操作一相当于节点往父亲走,操作二相当于往儿子方向走。然后以最小的步数到达叶子或者根。
考虑预处理数组,表示从点 出发,到达叶子的最小步数(其实就是子树的最小叶子深度-当前点深度),然后对该字符串所对应的 Tries
树的所有节点的+删去末尾字符到达该点的操作数
取个最小值即可。
插入字符串时需要更新 ,需要更新的也刚好就是插入时经过的所有节点。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; const int inf = 1e9 + 7; const int SZ = 26; template <typename T, typename K> struct Trie { struct node { bool is_terminal = false; int min_deep = inf; array<int, SZ> children{}; }; int cast(K val) { int ret = val - 'a'; assert(ret < SZ and ret >= 0); return ret; } vector<node> tree; Trie(K val) { tree.push_back(node()); } int insert(const T& sequence) { int cur = 0; int ans = sequence.size(); int cost = sequence.size(); for (int i = 0; i < (int)sequence.size(); i++) { K value = sequence[i]; if (tree[cur].children[cast(value)] == 0) { tree[cur].children[cast(value)] = (int)tree.size(); tree.push_back(node()); } cur = tree[cur].children[cast(value)]; --cost; ans = min(ans, cost + tree[cur].min_deep); tree[cur].min_deep = min(tree[cur].min_deep, cost); } tree[cur].is_terminal = true; return ans; } }; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n; cin >> n; Trie<string, char> trie('a'); for (int i = 0; i < n; i++) { string s; cin >> s; int ans = trie.insert(s); cout << ans << '\n'; } return 0; }
本文作者:~Lanly~
本文链接:https://www.cnblogs.com/Lanly/p/18513188
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步