本文是 AtCoder Beginner Conteset(ABC)377 的总结笔记。
A - Rearranging ABC Problem:A - Rearranging ABC
省略。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <bits/stdc++.h> using namespace std;typedef long long LL;typedef pair<int , int > PII;string s; void solve () { cin >> s; sort (s.begin (), s.end ()); if (s == "ABC" ) cout << "Yes" << endl; else cout << "No" << endl; } int main () { cin.tie (0 ); ios_base::sync_with_stdio (false ); solve (); return 0 ; }
B - Avoid Rook Attack Problem:B - Avoid Rook Attack
省略。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <bits/stdc++.h> using namespace std;typedef long long LL;typedef pair<int , int > PII;const int N = 10 ;char g[N][N];int col[N], row[N]; void solve () { for (int i = 0 ; i < 8 ; i++) cin >> g[i]; for (int i = 0 ; i < 8 ; i++) for (int j = 0 ; j < 8 ; j++) if (g[i][j] == '#' ) col[j] = row[i] = 1 ; int res = 0 ; for (int i = 0 ; i < 8 ; i++) for (int j = 0 ; j < 8 ; j++) if (!row[i] && !col[j]) res++; cout << res << endl; } int main () { cin.tie (0 ); ios_base::sync_with_stdio (false ); solve (); return 0 ; }
C - Avoid Knight Attack Problem:C - Avoid Knight Attack
STL
题目: 一个 N*N 的网格。骑士可以按照下面形式攻击周围的 8 个格子。
输出网格上有多少安全点。
约束条件:
思路: 题目的关键在于如何“去重”。
方法 1:(稍微麻烦一些) 使用 unordered_map<int, unordered_set<int> >
,其中,第一维度用来存储 x 坐标,第二维度用于存储 y 坐标,unordered_set
就会自动对 y 的坐标进行去重。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #include <bits/stdc++.h> using namespace std;typedef long long LL;typedef pair<int , int > PII;const int N = 2e5 + 10 ;int dx[8 ] = {2 , 1 , -1 , -2 , -2 , -1 , 1 , 2 }, dy[8 ] = {1 , 2 , 2 , 1 , -1 , -2 , -2 , -1 };int n, m;int a[N], b[N]; unordered_map<int , unordered_set<int > > occupation; bool check (int x, int y) { if (x < 1 || x > N || y < 1 || y > N) return false ; return true ; } void solve () { cin >> n >> m; for (int i = 1 ; i <= m; i++) { cin >> a[i] >> b[i]; occupation[a[i]].insert (b[i]); } for (int i = 1 ; i <= m; i++) { for (int j = 0 ; j < 8 ; j++) { int next_a = a[i] + dx[j]; int next_b = b[i] + dy[j]; if (check (next_a, next_b)) { occupation[next_a].insert (next_b); } } } LL oc = 0 ; for (auto [x, y] : occupation) oc += (LL)y.size (); cout << (LL)n * n - oc << endl; } int main () { cin.tie (0 ); ios_base::sync_with_stdio (false ); solve (); return 0 ; }
方法 2:(好写一些) set<pair<int,int>>
可以自动对整数对去重。但是 unordered_set<pair<int,int>>
不行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #include <bits/stdc++.h> using namespace std;typedef long long LL;typedef pair<int , int > PII;const int N = 2e5 + 10 ;int dx[8 ] = {2 , 1 , -1 , -2 , -2 , -1 , 1 , 2 }, dy[8 ] = {1 , 2 , 2 , 1 , -1 , -2 , -2 , -1 };int n, m;int a[N], b[N]; set<PII> occupation; bool check (int x, int y) { if (x < 1 || x > n || y < 1 || y > n) return false ; return true ; } void solve () { cin >> n >> m; for (int i = 1 ; i <= m; i++) { cin >> a[i] >> b[i]; occupation.insert ({a[i], b[i]}); } for (int i = 1 ; i <= m; i++) { for (int j = 0 ; j < 8 ; j++) { int next_a = a[i] + dx[j]; int next_b = b[i] + dy[j]; if (check (next_a, next_b)) occupation.insert ({next_a, next_b}); } } LL res = (LL)n * n - occupation.size (); cout << res << endl; } int main () { cin.tie (0 ); ios_base::sync_with_stdio (false ); solve (); return 0 ; }
D - Many Segments 2 Problem:D - Many Segments 2
贪心
题目: 给定长度为 的两个正整数数列 和
给定正整数
求出满足下面条件的整数对 的个数。
约束条件:
思路: 如果我们锁定一个 ,那么 最多可以取到那些 的区间的右端点的最小值-1
做法:从大到小枚举 ,对于每个 ,统计那些 的区间的右端点的最小值
优化:双指针。从大到小枚举 的时候, 的区间会逐渐变多,所以统计右端点的最小值,实时更新即可。
我们先说明一个细节:
例如:当前区间为 ,那么以 为左端点,不完全包含 的区间有多少个:4 个,分别是 这四个。这个数量刚好对应于 的 数量。
也就是说,如果定好了左边界 ,如果右边完全没有区间,那结果的数量刚好对应于 的数量
依次为基础,我们就可以逐渐的更新出所有的结果了。
时间复杂度为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 #include <bits/stdc++.h> using namespace std;typedef long long LL;typedef pair<int , int > PII;const int MAX_N = 2e5 + 10 ;struct node { int l, r; } a[MAX_N]; int n, m; int ans[MAX_N]; long long tot; bool cmp (node x, node y) { return x.l > y.l; } void solve () { cin >> n >> m; for (int i = 1 ; i <= n; i++) cin >> a[i].l >> a[i].r; for (int i = 1 ; i <= m + 1 ; i++) ans[i] = m + 1 ; for (int i = 1 ; i <= n; i++) ans[a[i].l] = min (ans[a[i].l], a[i].r); for (int i = m; i >= 1 ; i--) { ans[i] = min (ans[i], ans[i + 1 ]); tot += (ans[i] - i); } cout << tot << endl; } int main () { cin.tie (0 ); ios_base::sync_with_stdio (false ); solve (); return 0 ; }
E - Permute K times 2 Problem:E - Permute K times 2
(TODO)
题目: 给定一个 的排列
执行下面操作 K 次:
输出最后的 排列
约束条件:
思路: 这道题太抽象了,我还没理解,暂时先空下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include <bits/stdc++.h> using namespace std;typedef long long LL;typedef pair<int , int > PII;int n, p[200100 ]; long long k; int cnt[200100 ], ans[200100 ]; bool vis[200100 ]; long long fpow (long long a, long long b, long long m) { long long ans = 1 ; while (b) { if (b & 1 ) ans = (ans * a) % m; b >>= 1 ; a = (a * a) % m; } return ans; } void solve () { cin >> n >> k; for (int i = 1 ; i <= n; i++) cin >> p[i]; for (int i = 1 ; i <= n; i++) { if (!vis[i]) { vector<int > c; int j = i; while (!vis[j]) { vis[j] = 1 ; c.push_back (j); j = p[j]; } int len = c.size (); int temp = fpow (2 , k, len); for (int j = 0 ; j < len; j++) { ans[c[j]] = c[(j + temp) % len]; } } } for (int i = 1 ; i <= n; i++) cout << ans[i] << ' ' ; } int main () { cin.tie (0 ); ios_base::sync_with_stdio (false ); solve (); return 0 ; }
F - Avoid Queen Attack Problem:F - Avoid Queen Attack
TODO
题目: 有一个 N*N 的棋盘。棋盘上放置了 M 个棋子。棋子可以攻击 所在行,列,主次对角线上的任何位置。
请问棋盘上剩余多少位置不会被攻击到?
示例:
如下图所示的放置条件下,整个棋盘只有 2 个位置是安全的。
约束条件:
思路: TODO
G - Edit to Match Problem:G - Edit to Match
Trie 树模板题。TODO
题目: 给定 N 个字符串
令 ,进行下面两种操作之一
操作 1:删除 T 最后一个字母,cost 为 1 操作 2:给 T 最后添加一个字母,cost 为 1 求将 T 变为空串或者 中的任何一个串 所付出的最小代价。
约束条件
只包含小写字母
思路: 这是一道标准的 Tire 树模板题。
直观思路:先从 的尾部删除若干字符,删除后应该变成某个 的前缀,再开始添加字符。
使用字典树 / Tire 树 即可。
考虑对于 Trie 树上的每个节点,记录其到所有 的节点的最短距离 d。
这个距离等价于:从这个节点代表的字符串开始,至少添加多少个字符能够到达一个 。
查询的时候,从 代表的节点不断向上爬,并统计(向上爬的步数+d)的最小值,就是答案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 #include <bits/stdc++.h> using namespace std;typedef long long LL;typedef pair<int , int > PII;const int MAX_N = 2e5 + 10 ;int son[MAX_N][26 ], d[MAX_N], idx;int n;string s[MAX_N]; void insert (string s) { int p = 0 ; d[p] = min (d[p], (int )s.size ()); for (int i = 0 ; i < s.size (); i++) { int u = s[i] - 'a' ; if (!son[p][u]) { son[p][u] = ++idx; d[idx] = INT_MAX; } p = son[p][u]; d[p] = min (d[p], (int )s.size () - i - 1 ); } } int query (string s) { int p = 0 ; int ans = s.size (); for (int i = 0 ; i < s.size (); i++) { int u = s[i] - 'a' ; if (!son[p][u]) break ; p = son[p][u]; ans = min (ans, (int )s.size () - i - 1 + d[p]); } return ans; } void solve () { cin >> n; d[0 ] = INT_MAX; for (int i = 1 ; i <= n; i++) { cin >> s[i]; int cur_ans = query (s[i]); insert (s[i]); cout << cur_ans << endl; } } int main () { cin.tie (0 ); ios_base::sync_with_stdio (false ); solve (); return 0 ; }