AtCoder Beginner Contest 313
貌似这次很难,还好去吃烧烤了
发现原来G就是个类欧几里德算法,参考abc283 ex,更了个G
A - To Be Saikyo (abc313 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 n; cin >> n; vector<int> a(n); for (auto& i : a) cin >> i; int ans = max(0, *max_element(a.begin() + 1, a.end()) + 1 - a.front()); cout << ans << '\n'; return 0; }
B - Who is Saikyo? (abc313 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 n, m; cin >> n >> m; vector<int> du(n); for (int i = 0; i < m; ++i) { int u, v; cin >> u >> v; --u, --v; du[v]++; } int zero = count(du.begin(), du.end(), 0); if (zero != 1) cout << -1 << '\n'; else cout << find(du.begin(), du.end(), 0) - du.begin() + 1 << '\n'; return 0; }
C - Approximate Equalization 2 (abc313 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> a(n); LL sum = 0; for (auto& i : a) cin >> i; sort(a.begin(), a.end()); sum = accumulate(a.begin(), a.end(), 0ll); int div = sum / n; int mod = sum % n; LL cnt = 0; for (int i = 0; i < n; ++i) { cnt += abs((div + (i + mod >= n) - a[i])); } cout << cnt / 2 << '\n'; return 0; }
D - Odd or Even (abc313 D)
题目大意
交互题。
有个 数。
给定奇数,每次可以问其中 个数的异或值。
最多 次请求,还原出这 个数。
解题思路
考虑的情况,发现可以问
1 2 3
1 2 4
1 3 4
三个请求结果的异或值就是的值。因为 为奇数,每次询问都包括 ,最终 的次数是奇数,而其他的都有一次询问没有被包含,因此出现次数是偶数,异或都消失。答案就是 的值。
同理,如果再加上 2 3 4
,结合1 2 3
和1 2 4
,那就能得出的值。
由此可以对前 个数 进行询问,每次询问剔除一个数,得到的个结果,取个始终包含第个数的结果进行异或,得到的 就是的值,因此我们可以得到前 个数的值。
前 个数都知道了,要得到第 个数,那就询问 的异或值,再异或就得到了 的值。依次就可以得到剩下的所有数了。
神奇的代码
#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, k; cin >> n >> k; vector<int> ans(n); auto solve = [&](int l, int r) { vector<int> tmp(r - l); vector<int> q; for (int i = l; i < r; ++i) { q.clear(); for (int j = l; j < r; ++j) if (j != i) q.push_back(j); cout << "?"; for (auto& i : q) cout << ' ' << i + 1; cout << endl; cin >> tmp[i - l]; } for (int i = l; i < r; ++i) { for (int j = l; j < r; ++j) { if (j != i) { ans[i] ^= tmp[j]; } } } }; solve(0, k + 1); for (int i = k + 1; i < n; i++) { cout << "?"; for (int j = i; j > i - k; --j) { cout << ' ' << j + 1; } cout << endl; cin >> ans[i]; for (int j = i - 1; j > i - k; --j) ans[i] ^= ans[j]; } cout << "!"; for (auto& i : ans) cout << ' ' << i; cout << endl; return 0; }
E - Duplicate (abc313 E)
题目大意
给定一个数字字符串,每次进行一次操作,问经历了多少次,最终的字符串的长度为。
操作问,得到一个字符串,依次对于 的每个数 (除了最后一个),重复 次添加到字符串 的末尾。
如果最终长度不能为,则输出 。
解题思路
首先考虑何时。
每次操作实际上是删掉一个数,如果每次操作是生成一堆的话,容易发现不会造成 的情况,但如果生成了其他的数,那就会叠罗汉一样不断增加,比如生成了个 ,那它们会不断叠起来,而每次删除都只删一个数,那最终就会无限长了。
因此每个非的数的后面一个数 一定是,否则就会无限长。
考虑如何求答案,我们考虑消除每个数所需要的次数,这个次数既然包括消除它本身,也包括消除其产生的所有的(不包括原本的 )。
对于每个 ,自然会需要一次操作将其消除,
而对于一个非 数,其前一个数一定是 ,要考虑消除因它而产生的的个数。
由于我们每操作一次,这个就会产生 个 出来,那当这个数 变成最后一个数时,假设我们已经进行了 次操作,包括消除这个的操作带来的一次 个 ,一共生成了 个,因此我们需要次操作才能将这个数以及其带来的所有 消除掉。
而如何求 ,就是消除所需要的次数,因此得倒过来依次考虑每一个数消除所需要的次数。
即设 表示消除 需要的次数,那么
上述转移式就是三部分:
- 消除的次数
- 消除的次数
- 消除带来的所有 的次数
最后答案就是,初始条件
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; const LL mo = 998244353; int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n; string s; cin >> n >> s; auto ok = [&]() { for (int i = 0; i < n; ++i) { if (s[i] != '1' && i + 1 != n && s[i + 1] != '1') return false; } return true; }; if (!ok()) cout << -1 << '\n'; else { LL ans = 0; for (int i = n - 1; i >= 1; --i) { ans += 1ll * (s[i] - '1') * (ans + 1) + 1; ans = ans % mo; } cout << ans << '\n'; } return 0; }
F - Flip Machines (abc313 F)
题目大意
<++>
解题思路
<++>
神奇的代码
G - Redistribution of Piles (abc313 G)
题目大意
给定堆石头,可以进行以下两种操作:
- 从所有还有石头的堆里面都拿走一个石头,放到包里。
- 从包中取 个石子,给每堆石子都放上一个石子。
两种操作可以以任何顺序执行任意多次,问操作执行之后, 堆石子的局面数的个数。
解题思路
首先考虑两种操作的关系,容易发现对于最终的某种局面,其操作的顺序都可以通过先进行若干次操作一,再进行若干次操作二得到。即先执行操作一,之后才执行操作二。因为执行了操作二后再执行操作一,相当于原来撤销了原来的操作二。
由此我们考虑执行了多少次操作一,再次基础上我们收集了个石头,之后便可执行 次操作二,每执行一次操作二,会发现得到的都是一个不同的局面(对初始石子堆排个序容易发现)。由此我们对所有的操作次数求个和即为答案。
注意到每次执行操作一后,增加的数量不一定是一个定值,这取决于当前还剩下多少堆非零的石子堆,因此我们先对石子堆从小到大排序,枚举前堆石子变成零,此时枚举的操作一的操作数范围为。
当操作一的次数是 时,可进行的操作二的次数为 ,注意这里的下标都是从开始的,。
因为此时操作一的次数范围为,累加一下即为
注意到每一项都是形如的形式,可用类欧几里德算法在时间内算出其和,即
枚举所有的求和,即为答案。
总的时间复杂度是
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; const LL mo = 998244353; LL floor_sum(LL a, LL b, LL c, LL n) { if (n < 0) return 0; LL val = 0; if (a >= c) { val += n * (n + 1) / 2 * (a / c) % mo; a %= c; } if (b >= c) { val += (n + 1) * (b / c) % mo; b %= c; } LL m = (a * n + b) / c; val += n * m % mo - floor_sum(c, c - b - 1, a, m - 1); val %= mo; val = (val + mo) % mo; return val; } int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n; cin >> n; vector<int> a(n); for (auto& i : a) cin >> i; sort(a.begin(), a.end()); LL ans = (a.back() + 1) % mo; LL presum = a[0]; for (int i = 1; i < n; ++i) { ans += floor_sum(n - i, presum, n, a[i]) - floor_sum(n - i, presum, n, a[i - 1]); ans %= mo; ans = (ans + mo) % mo; presum += a[i]; } cout << ans << '\n'; return 0; }
Ex - Group Photo (abc313 Ex)
题目大意
<++>
解题思路
<++>
神奇的代码
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现