AtCoder Beginner Contest 356
A - Subsegment Reverse (abc356 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, a, b; cin >> n >> a >> b; --a; vector<int> ans(n); iota(ans.begin(), ans.end(), 1); reverse(ans.begin() + a, ans.begin() + b); for (auto x : ans) cout << x << ' '; cout << '\n'; return 0; }
B - Nutrients (abc356 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> a(m); for (auto& x : a) cin >> x; while (n--) { for (auto& x : a) { int s; cin >> s; x -= s; } } bool ok = true; for (auto x : a) { ok &= x <= 0; } if (ok) { cout << "Yes" << '\n'; } else { cout << "No" << '\n'; } return 0; }
C - Keys (abc356 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, m, k; cin >> n >> m >> k; vector<pair<vector<int>, int>> a(m); for (auto& [v, c] : a) { int x; cin >> x; v.resize(x); for (auto& x : v) { cin >> x; --x; } string s; cin >> s; c = s[0] == 'o'; } int ans = 0; int up = (1 << n); for (int i = 0; i < up; ++i) { bool ok = true; for (auto& [v, c] : a) { int cnt = 0; for (auto x : v) { if (i & (1 << x)) { ++cnt; } } if ((cnt >= k) ^ c) { ok = false; break; } } ans += ok; } cout << ans << '\n'; return 0; }
D - Masked Popcount (abc356 D)
题目大意
给定,求
表示 二进制下 的个数。
解题思路
高达 ,直接枚举会超时。
考虑贡献转换。
考虑到答案来自于二进制下的个数。 由于运算的特性,这些实际都是来自于的每一个。 我们需要考虑二进制下每一个 对答案的贡献,即有多少个 ,使得 后该位是 。
假设的二进制表示为 ,考虑第 位上的,思考有多少个 ,使得 的第位是 。
考虑如何计算 的数量,首先, 的第 位一定是 ,然后就剩下低位
和高位
的情况数。低位就是低于位的那些位数的取值,高于 位的就是高于 位的位数取值。这个计数问题其实和数位差不多。
由于有最大值 的限制,低位
的情况数会依赖于高位
,为方便表述,设高位是,当前位是 ,低位是 ,比如 ,当前第 位(从 开始),则 。然后情况数其实就分两种:
- 如果
高位
取值和的高位
不一致,则低位
取值没有限制,因此低位
的情况数是 ,而高位
的情况数是,若此时 的第 位是 ,则其贡献(出现的次数)为 。 - 如果
高位
取值和的高位
一致,则当前位
和低位
的都会收到的限制。如果此时 ,则说明该位不能取 ,则 的第 位没有贡献。否则 ,低位
的情况数就有 种情况,而高位
的 情况数只有种,若此时 的第 位是 ,则其贡献(出现的次数)为 。
综上,考虑第位,其中 的高位是 ,当前位是 ,低位是 ,若 的第 位是 ,则其对答案的贡献(满足 的第 位是 的 的个数)为 。
神奇的代码
#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); LL n, m; cin >> n >> m; LL ans = 0; LL up = n >> 1, middle = n & 1, down = 0, cnt = 1; for (int i = 0; i < 60; ++i) { if ((m >> i) & 1) { ans += 1ll * up * cnt % mo + middle * (down + 1); ans %= mo; } down = down | (middle << i); middle = up & 1; up >>= 1; cnt <<= 1; } cout << ans << '\n'; return 0; }
E - Max/Min (abc356 E)
题目大意
给定一个数组,求 。
解题思路
作除法始终是最大值除以最小值,因此可以先对进行排序再计算,这不会影响答案。
对 从小到大排序后,考虑枚举 ,然后计算 情况对答案的贡献,即枚举最大值。
一个比较明显的观察是,可能会有若干个 ,使得 是同样的值。那我们可以把这些值合并地来算,即值
个数
。
事实上可以通过数论分块来求解,的值的可能数量和因子个数同一个数量级,即个,而造成该取整结果的的取值就是数论分块里的两个边界 ,可以在 中二分得到对应位置,因而得到个数
。而这复杂度是,会超时。
数论分块复杂度是根号级别,在这里用不了,只能另寻它路。
这次考虑枚举 ,然后计算 情况对答案的贡献,即枚举最小值。
同样考虑贡献转换,原本的想法是求的数量,然后累计。
我们将其看成,然后重组一下,变成求 的数量。
即原本是求的数量,现在求的数量。
这里的贡献转换就是将对答案有的贡献,拆成了 ,即,然后合并所有的(或),求其个数。
现在我们的视角转换成求 的数量,即枚举,求的的数量。很明显通过在数组二分,就能求得的数量了。
由于,那么这里枚举的的数量就是。每次枚举都有一个二分的,因此总的时间复杂度是。
如果所有的都很小,比如都是 ,那时间复杂度就是 ,又炸了!
但看这个式子,非常像对数求和,如果唯一,那复杂度就是,是可过的。
因此,如果有重复的,那么我们就合并重复的数,并记录表示 的数量,然后考虑怎么修正一下答案的计算。
合并相同的数,并从小到大排序,然后枚举当前的 ,再枚举 ,求得第一个的 ,那么此时的数量就是。而后者就是一个关于数组的后缀和,预处理一下就能得到。
然后对于相同数之间的贡献,则是。
最终的答案就是两者的和。
神奇的代码
#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> r(n); for (auto& x : r) cin >> x; map<int, int> s; for (auto& i : r) s[i]++; vector<int> a; vector<int> sum; for (auto& [x, y] : s) { a.push_back(x); sum.push_back(y); } vector<int> suf(sum.size()); partial_sum(sum.rbegin(), sum.rend(), suf.rbegin(), plus<int>()); LL ans = 0; n = a.size(); for (int i = 0; i < n; ++i) { int x = a[i]; int pos = i + 1; ans += 1ll * sum[i] * (sum[i] - 1) / 2; while (pos < n) { pos = lower_bound(a.begin() + pos, a.end(), x) - a.begin(); if (pos < n) ans += 1ll * sum[i] * suf[pos]; x += a[i]; } } cout << ans << '\n'; return 0; }
F - Distance Component Size Query (abc356 F)
题目大意
<++>
解题思路
<++>
神奇的代码
G - Freestyle (abc356 G)
题目大意
<++>
解题思路
<++>
神奇的代码
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现