AtCoder Beginner Contest 362
A - Buy a Pen (abc362 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); int r, g, b; string c; cin >> r >> g >> b >> c; if (c[0] == 'R') { r = 999; } else if (c[0] == 'G') { g = 999; } else { b = 999; } cout << min({r, g, b}) << '\n'; return 0; }
B - Right Triangle (abc362 B)
题目大意
给定三点坐标,问是否形成直角三角形。
解题思路
枚举直角点,然后向量点积判断是否成90度即可。
神奇的代码
#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); array<array<int, 2>, 3> p; for (auto& x : p) cin >> x[0] >> x[1]; auto vertical = [](array<int, 2>& a, array<int, 2>& b, array<int, 2>& c) { return (a[0] - b[0]) * (a[0] - c[0]) + (a[1] - b[1]) * (a[1] - c[1]) == 0; }; if (vertical(p[0], p[1], p[2]) || vertical(p[1], p[2], p[0]) || vertical(p[2], p[0], p[1])) { cout << "Yes" << '\n'; } else { cout << "No" << '\n'; } return 0; }
C - Sum = 0 (abc362 C)
题目大意
给定两个个数的数组 ,构造 个数 ,满足:
解题思路
先假定,此时如果则无解。
否则遍历,对于每个 ,依次增大 ,直到 或者。贪心增加即可。
神奇的代码
#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> l(n), r(n); for (int i = 0; i < n; i++) { cin >> l[i] >> r[i]; } vector<int> x = l; LL sum = accumulate(l.begin(), l.end(), 0ll); for (int i = 0; i < n; i++) { if (sum < 0) { int c = min(-sum, 0ll + r[i] - l[i]); x[i] += c; sum += c; } } if (sum != 0) { cout << "No" << '\n'; } else { cout << "Yes" << '\n'; for (int i = 0; i < n; i++) { cout << x[i] << " \n"[i == n - 1]; } } return 0; }
D - Shortest Path 3 (abc362 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, m; cin >> n >> m; vector<int> a(n); for (auto& x : a) cin >> x; vector<vector<array<int, 2>>> edge(n); for (int i = 0; i < m; i++) { int u, v, w; cin >> u >> v >> w; --u, --v; edge[u].push_back({v, w}); edge[v].push_back({u, w}); } vector<LL> dis(n, 1e18); dis[0] = a[0]; priority_queue<pair<LL, int>, vector<pair<LL, int>>, greater<pair<LL, int>>> team; team.push({dis[0], 0}); while (!team.empty()) { auto [d, u] = team.top(); team.pop(); if (dis[u] < d) continue; for (auto& [v, w] : edge[u]) { if (dis[v] > dis[u] + w + a[v]) { dis[v] = dis[u] + w + a[v]; team.push({dis[v], v}); } } } for (int i = 1; i < n; i++) cout << dis[i] << " \n"[i == n - 1]; return 0; }
E - Count Arithmetic Subsequences (abc362 E)
题目大意
给定个数 ,对于 ,问长度为 的 的子序列中,是等差数列的数量。
解题思路
长度为的等差数列可以直接算出来,因此考虑长度 的情况。
考虑如何统计等差数列,比如考虑枚举公差,统计不同公差下的子序列数量。
虽然公差的范围,但考虑到公差是两个数的差,其取值实际只有 个,而 ,可以考虑枚举公差。然后统计该公差下各个长度的子序列数量,累计求和即为答案。
枚举公差 ,剩下就是考虑如何统计其子序列的个数。考虑朴素搜索,即从左到右依次考虑每个数选或不选,选的话要保证构成公差为 的等差数列,需要得知上一个选的数是什么,同时还要保留我已经选了多少个数。
据此容易想到就是一个朴素的 : 表示 考虑前个数,且选择了第 个数,且已经选了 个数的等差为 的子序列数量。 转移则枚举,满足 ,则 。即。
枚举公差 , 状态 ,转移 ,总复杂度是 ,是无法通过的,考虑优化转移。
对于一个转移,其中 ,遍历所有的公差时,其实 只有一个时,会发生这个转移。 也就是说,固定了公差,我们就可以预处理出状态转移的前继状态,即可以从什么 转移过来,预处理的复杂度是,随后在求时,转移就无需遍历 ,直接遍历预处理的转移即可。
这样预处理之后,求的复杂度是多少呢?因为每个公差预处理出来的 的前继转移 的数量不同,但注意到一个转移,其中 ,遍历所有的公差时,其实 只有一个时才发生这个转移,纵观所有的这类转移,其数量有个,但其因为公差 被打散在这 次求 里,所以所有公差,每个公差遍历预处理的转移前继状态,总的复杂度是个状态+ 次转移,总的复杂度还是 。
神奇的代码
#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<int> a(n); for (auto& x : a) cin >> x; vector<int> diff; for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { diff.push_back(a[j] - a[i]); } } sort(diff.begin(), diff.end()); diff.erase(unique(diff.begin(), diff.end()), diff.end()); vector<int> ans(n + 1, 0); ans[1] = n; if (n > 1) ans[2] = n * (n - 1) / 2; for (auto d : diff) { vector<vector<int>> tr(n); for (int i = 0; i < n; ++i) for (int j = 0; j < i; ++j) if (a[i] - a[j] == d) tr[i].push_back(j); vector<vector<int>> dp(n, vector<int>(n + 1, 0)); for (int i = 0; i < n; ++i) { dp[i][1] = 1; for (int j = 1; j <= i; ++j) { for (auto& k : tr[i]) { dp[i][j + 1] += dp[k][j]; if (dp[i][j + 1] >= mo) { dp[i][j + 1] -= mo; } } } } for (int i = 0; i < n; ++i) { for (int j = 3; j <= n; ++j) { ans[j] += dp[i][j]; if (ans[j] >= mo) { ans[j] -= mo; } } } } for (int i = 1; i <= n; ++i) { cout << ans[i] << " \n"[i == n]; } return 0; }
F - Perfect Matching on a Tree (abc362 F)
题目大意
给定一棵树,俩俩配对,收益是两个点的最短路边数。
构造配对方案,使得收益最大。
解题思路
每次收益是边数,配对的收益和最大,换个角度考虑贡献,认为考虑每条边,其出现在配对最短路的次数。
一条边将树拆成两个连通块,假设点数分别为,如果配对的两个点分别在这两个连通块里,这个这条边对答案就有的贡献 。那一条边对答案的最大贡献即为。
所有边的最大贡献和,即为收益的上界,考虑这个上界能否取到。很显然,对于一些 很大或很小的,都比较小,这些边的贡献上界很容易取到,因此关键要考虑 左右的边。
而这些边实际是在树的重心附近,考虑重心,其特点是最大的儿子数不超过 ,重心有好几个儿子,我们的配对目标是,配对的两个点来自于不同的儿子子树。这样每个儿子子树里的边都可以取到上界。
剩下的问题就是如何选择儿子子树。事实上,考虑abc359f,其实构造方法是一样的。
将每个儿子子树看成一个点,子树的点树视为该点的度数,每取两个子树的儿子配对,相当于给这两个子树点连边。然后最终每个点的度数满足要求。
因此构造方法为,每次选择一个儿子子树最大的和非最大的,配对。动态维护子树大小。
如果点数是奇数,则抛弃重心,否则也把重心视为一个子树。
有更简单的构造方法,从重心进行,记录遍历的每一个点,记为 ,由于最大的子树大小小于 ,因此直接连边 ,这两个点一定在不同子树的。
神奇的代码
#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<vector<int>> edge(n); for (int i = 0; i < n - 1; i++) { int u, v; cin >> u >> v; u--; v--; edge[u].push_back(v); edge[v].push_back(u); } vector<int> son(n, 0), weight(n, 0); int root = 0; auto dfs = [&](auto&& dfs, int u, int fa) -> void { son[u] = 1; for (auto v : edge[u]) { if (v == fa) continue; dfs(dfs, v, u); son[u] += son[v]; weight[u] = max(weight[u], son[v]); } weight[u] = max(weight[u], n - son[u]); if (weight[u] <= n / 2) root = u; }; dfs(dfs, 0, 0); vector<vector<int>> child; auto dfs2 = [&](auto&& dfs2, int u, int fa, vector<int>& cc) -> void { cc.push_back(u); for (auto v : edge[u]) { if (v == fa) continue; dfs2(dfs2, v, u, cc); } }; for (auto& u : edge[root]) { vector<int> cc; dfs2(dfs2, u, root, cc); child.push_back(cc); } if (n % 2 == 0) child.push_back({root}); auto cmp = [&](const int a, const int b) -> bool { return child[a].size() < child[b].size(); }; priority_queue<int, vector<int>, decltype(cmp)> pq(cmp); for (int i = 0; i < child.size(); ++i) { pq.push(i); } for (int i = 0; i < n / 2; ++i) { auto tu = pq.top(); pq.pop(); auto tv = pq.top(); pq.pop(); int u = child[tu].back(); int v = child[tv].back(); child[tu].pop_back(); child[tv].pop_back(); if (child[tu].size() > 0) pq.push(tu); if (child[tv].size() > 0) pq.push(tv); cout << u + 1 << " " << v + 1 << '\n'; } return 0; }
G - Count Substring Query (abc362 G)
题目大意
给定一个字符串,回答 个询问。
每个询问给定一个字符串 ,问字符串 在字符串 里的出现次数。
解题思路
此即为后缀自动机的一个节点的大小,贴个模板即可。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; class SAM { enum { len = 1000005, vary = 26 }; int trans[len << 1][vary]; int maxlen[len << 1]; int link[len << 1]; int cnt[len << 1]; int tot; int last; public: SAM() { tot = last = 0; link[tot] = -1; maxlen[tot] = 0; ++tot; } void clear() { for (int i = 0; i < tot; ++i) { for (int j = 0; j < vary; ++j) { trans[i][j] = 0; } maxlen[i] = link[i] = cnt[i] = 0; } tot = last = 0; link[tot] = -1; maxlen[tot] = 0; ++tot; } void insert(int s) { int cur = tot++; maxlen[cur] = maxlen[last] + 1; int p = last; for (; p != -1 && !trans[p][s]; p = link[p]) trans[p][s] = cur; if (p == -1) link[cur] = 0; else { int q = trans[p][s]; if (maxlen[q] == maxlen[p] + 1) link[cur] = q; else { int clone = tot++; maxlen[clone] = maxlen[p] + 1; link[clone] = link[q]; for (int i = 0; i < vary; ++i) trans[clone][i] = trans[q][i]; for (; p != -1 && trans[p][s] == q; p = link[p]) trans[p][s] = clone; link[q] = link[cur] = clone; } } cnt[cur] = 1; last = cur; } int tong[len]; int sa[len << 1]; void build() { tong[0] = 0; for (int i = 1; i < tot; ++i) ++tong[maxlen[i]]; for (int i = 1; i < len; ++i) tong[i] += tong[i - 1]; for (int i = 1; i < tot; ++i) sa[tong[maxlen[i]]--] = i; for (int i = tot - 1; i >= 0; --i) { int p = sa[i]; cnt[link[p]] += cnt[p]; } } int solve(string& t) { int cur = 0; for (auto c : t) { auto s = c - 'a'; if (trans[cur][s] == 0) return 0; cur = trans[cur][s]; } return cnt[cur]; } } sam; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); string s; cin >> s; for (auto c : s) sam.insert(c - 'a'); sam.build(); int q; cin >> q; while (q--) { string t; cin >> t; int ans = sam.solve(t); cout << ans << '\n'; } return 0; }
本文作者:~Lanly~
本文链接:https://www.cnblogs.com/Lanly/p/18301039
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】