AtCoder Beginner Contest 372
省流版
- A. 暴力即可
- B. 转换3进制即可
- C. 考虑答案的组成,仅修改发生变化的部分即可
- D. 维护答案数组,考虑枚举 对哪些 有贡献,通过单调栈找到对应的区间 ,通过差分维护区间加法即可
- E. 并查集维护连通块,维护点标号大小,合并时启发式合并,查询则从对应连通块的最大值往前找次即可
- F. 考虑朴素,发现有效转移只有 个,记忆化搜索即可
A - delete . (abc372 A)
题目大意
给定一个字符串,删除其中的.
。
解题思路
依次判断每个字符,如果不是.
就输出。
python可以一行,
神奇的代码
print(input().replace('.', ''))
B - 3^A (abc372 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 m; cin >> m; vector<int> ans; for (int i = 0; i <= 10; ++i) { int cnt = m % 3; while (cnt--) { ans.push_back(i); } m /= 3; } cout << ans.size() << '\n'; for (auto i : ans) cout << i << " "; cout << '\n'; return 0; }
C - Count ABC Again (abc372 C)
题目大意
给定一个字符串,进行以下次操作。
每次操作,将。问操作完后,字符串 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); int n, q; string s; cin >> n >> q >> s; int ans = 0; auto in_range = [&](int i) { return i >= 0 && i < n; }; auto check = [&](int x) { for (int i = 0; i < 3; ++i) if (!in_range(x + i) || s[x + i] != 'A' + i) return false; return true; }; for (int i = 0; i < n; ++i) { ans += check(i); } while (q--) { int x; string c; cin >> x >> c; --x; for (int i = x - 2; i <= x; ++i) { ans -= check(i); } s[x] = c[0]; for (int i = x - 2; i <= x; ++i) { ans += check(i); } cout << ans << '\n'; } return 0; }
D - Buildings (abc372 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; cin >> n; vector<int> h(n); for (auto& x : h) cin >> x; stack<int> s; vector<int> diff(n + 1); for (int i = n - 1; i >= 0; i--) { while (!s.empty() && h[s.top()] < h[i]) { diff[i]++; diff[s.top()]--; s.pop(); } s.push(i); } while (!s.empty()) { diff[0]++; diff[s.top()]--; s.pop(); } int ans = 0; for (int i = 0; i < n; ++i) { ans += diff[i]; cout << ans << " \n"[i == n - 1]; } return 0; }
E - K-th Largest Connected Components (abc372 E)
题目大意
给定个点,初始无边。维护两类操作。
1 u v
,连一条无向边2 v k
,找所在连通块的第 大的点的标号。
。
解题思路
注意到,如果能用 维护一个连通块的所有点的标号,那么直接从 rbegin()
暴力数个就是答案,
如果维护这个 呢?连通块的信息自然用并查集维护,当合并两个连通块时,其 也要合并,我们可以将小的 合并到大的 中,即启发式合并,每次合并 最坏的复杂度是 ,但最坏情况下,合并后的 大小会翻倍,最多翻倍 次就达到 个点,即最坏情况的出现次数只有 次,因此启发式合并的时间复杂度是 。
维护好 后,直接从 数 个点即为答案。特判少于个点的情况。
时间复杂度是
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; class dsu { public: vector<int> p; vector<int> sz; vector<set<int>> node; int n; dsu(int _n) : n(_n) { p.resize(n); sz.resize(n); node.resize(n); iota(p.begin(), p.end(), 0); fill(sz.begin(), sz.end(), 1); for (int i = 0; i < n; i++) { node[i].insert(i); } } inline int get(int x) { return (x == p[x] ? x : (p[x] = get(p[x]))); } inline bool unite(int x, int y) { x = get(x); y = get(y); if (x != y) { if (sz[x] < sz[y]) swap(x, y); node[x].insert(node[y].begin(), node[y].end()); p[y] = x; sz[x] += sz[y]; return true; } return false; } }; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n, q; cin >> n >> q; dsu d(n); while (q--) { int op; cin >> op; if (op == 1) { int u, v; cin >> u >> v; --u, --v; d.unite(u, v); } else { int v, k; cin >> v >> k; --v; int fa = d.get(v); if (d.node[fa].size() < k) { cout << -1 << '\n'; } else { auto it = d.node[fa].rbegin(); advance(it, k - 1); cout << *it + 1 << '\n'; } } } return 0; }
F - Teleporting Takahashi 2 (abc372 F)
题目大意
给定一张有向图,它首先是个环,然后在此基础上加了条有向边。
问从 号点出发,走 个点,所形成的点的路径 的情况数。
解题思路
考虑朴素搜索,设表示从 号点走 步的方案数,显然其答案就是 。
但这复杂度是 ,显然过不了。
但注意到有很多点 的 其实只有一个即 ,即只有一种走法,它们的转移显然是没必要的。我们可以只考虑有分叉点的 。而这有分叉点的 最多只有 个。因此如果我们的转移是从 分叉点,这样的时间复杂度就是 , 是可以通过的。
记当前点,然后找到 后第一个分叉点 , 其出度,那么 ,转移后可以通过简单的运算得到,即。
有效的就只有 个,对 进行记忆化搜索,复杂度即为 。因为用来记忆化会超时,所以只好离散化有效的 ,转成数组来记忆化了。
神奇的代码
#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, m, k; cin >> n >> m >> k; vector<vector<int>> edge(n); for (int i = 0; i < n; i++) { edge[i].push_back((i + 1) % n); } vector<int> tr; for (int i = 0; i < m; i++) { int u, v; cin >> u >> v; u--; v--; edge[u].push_back(v); tr.push_back(u); } for (int i = 0; i < n; i++) { sort(edge[i].begin(), edge[i].end()); edge[i].erase(unique(edge[i].begin(), edge[i].end()), edge[i].end()); } sort(tr.begin(), tr.end()); tr.erase(unique(tr.begin(), tr.end()), tr.end()); auto calc = [&](int u, int v) -> int { if (u > v) v += n; return v - u; }; int ans = 0; if (m == 0) { ans = 1; } else { vector<int> nxt(n); for (int i = 0; i < n; i++) { auto it = lower_bound(tr.begin(), tr.end(), i); if (it == tr.end()) it = tr.begin(); nxt[i] = *it; } vector<int> id(n, -1); int cnt = 0; auto dfs_pre = [&](auto& dfs, int u) -> void { if (id[u] != -1) return; id[u] = cnt++; int it = nxt[u]; for (int v : edge[it]) { dfs(dfs, v); } }; dfs_pre(dfs_pre, 0); vector<vector<int>> dp(k + 1, vector<int>(cnt, -1)); auto dfs = [&](auto& dfs, int u, int k) -> int { if (k == 0) { return 1; } if (dp[k][id[u]] != -1) return dp[k][id[u]]; int it = nxt[u]; int sum = 0; int nxt = (u + 1) % n; int cost = calc(u, it); if (cost >= k) return 1; for (int v : edge[it]) { sum += dfs(dfs, v, k - cost - 1); if (sum >= mo) sum -= mo; } return dp[k][id[u]] = sum; }; ans = dfs(dfs, 0, k); } cout << ans << '\n'; return 0; }
G - Ax + By < C (abc372 G)
题目大意
给定三个个数的数组,问 的数量,满足
- ,对任意
多组数据。
解题思路
一眼只会。
神奇的代码
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效