AtCoder Beginner Contest 380
省流版
- A. 计数判断即可
- B. 计数统计即可
- C. 模拟即可
- D. 注意到字符串是左右大小写镜像,从长度大往小依次考虑实际所处的位置,维护镜像次数集合
- E. 并查集维护连通性,并尝试与左右俩格子合并即可
- F. 博弈,状态数只有 ,直接记忆化搜索即可
- G. 枚举打乱起始位置,全排列分成三部分,考虑逆序对来源的个部分,注意到打乱部分的逆序对期望数量为定值,其余 部分用权值树状数组或权值线段树维护即可
A - 123233 (abc380 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); string s; cin >> s; bool ok = true; for (int i = 1; i <= 3; ++i) ok &= count(s.begin(), s.end(), i + '0') == i; if (ok) cout << "Yes" << '\n'; else cout << "No" << '\n'; return 0; }
B - Hurdle Parsing (abc380 B)
题目大意
给定一个包含-|
的字符串。统计每两个|
之间有多少个-
。
解题思路
找到连续的两个|
的下标,然后其差即为答案。Python
可以一行。
神奇的代码
print(' '.join([str(len(s)) for s in input()[1:-1].split('|')]))
C - Move Segment (abc380 C)
题目大意
给定一个01
子串。
连续的1
视为一个块。
现将第个块移动到第 个块前面。
解题思路
按照题意,找到第个块的下标,然后复制即可。下述是Python
代码,split('0')
可以轻易找到第个块,然后移动即可。
神奇的代码
n, k = map(int, input().split()) k -= 1 s = input().split('0') one = [pos for pos, i in enumerate(s) if i] kk = s[one[k]] s.pop(one[k]) s[one[k - 1]] += kk + '0' print('0'.join(s))
D - Strange Mirroring (abc380 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); string s; int q; cin >> s >> q; vector<LL> round{int(s.size())}; auto dfs = [&](auto dfs, LL k, int sign) -> char { if (k < s.size()) { if (sign) { if (isupper(s[k])) { return tolower(s[k]); } else { return toupper(s[k]); } } else { return s[k]; } } auto pos = upper_bound(round.begin(), round.end(), k); LL len = *pos / 2; return dfs(dfs, k - len, sign ^ 1); }; while (q--) { LL k; cin >> k; --k; while (round.back() <= k) { round.push_back(round.back() * 2); } cout << dfs(dfs, k, 0) << ' '; } cout << '\n'; return 0; }
E - 1D Bucket Tool (abc380 E)
题目大意
从左到右 个格子,第 个格子颜色为 。
维护 个查询,分两种:
1 x c
:将第个格子连同周围与其同色的格子涂成颜色2 c
:问颜色的格子数
解题思路
如果一个格子颜色与周围相同,则它们就会连通,成为一个整体,我们可以用并查集维护这个连通性。
对于操作,从并查集就很方便的修改一个整体的颜色,进而维护每种颜色的格子数。但修改颜色后,需要看看这个整体与左右两个格子的颜色是否相同,能否合并成新的整体。
为了能找到这个整体的左右相邻格子,并查集需要维护该整体的最左和最右的格子,然后根据颜色决定是否合并。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; class dsu { public: vector<int> p; vector<int> l; vector<int> r; vector<int> sz; vector<int> col; int n; dsu(int _n) : n(_n) { p.resize(n); col.resize(n); sz.resize(n); l.resize(n); r.resize(n); iota(l.begin(), l.end(), 0); iota(r.begin(), r.end(), 0); iota(p.begin(), p.end(), 0); iota(col.begin(), col.end(), 0); fill(sz.begin(), sz.end(), 1); } 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) { p[x] = y; sz[y] += sz[x]; l[y] = min(l[y], l[x]); r[y] = max(r[y], r[x]); 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); vector<int> cnt(n, 1); while (q--) { int op; cin >> op; if (op == 1) { int x, c; cin >> x >> c; x--; --c; int fx = d.get(x); cnt[d.col[fx]] -= d.sz[fx]; d.col[fx] = c; cnt[d.col[fx]] += d.sz[fx]; for (auto& nei : {d.l[fx] - 1, d.r[fx] + 1}) { if (nei >= 0 && nei < n) { int fnei = d.get(nei); if (fnei != fx && d.col[fnei] == c) { d.unite(fx, fnei); } } } } else { int c; cin >> c; --c; cout << cnt[c] << '\n'; } } return 0; }
F - Exchange Game (abc380 F)
题目大意
高桥和青木手里各有张牌,桌子上有 张牌。牌上写了数字。
高桥和青木轮流操作,直到无法操作的人输。
操作为,将手里的一张牌 放到桌子上,如果桌上有数字小于 的牌,他可以拿一张到自己手上,当然也可以选择不拿。
高桥先手,问最优策略下谁赢。
解题思路
博弈,从卡牌在谁手中的角度思考状态数,只有 ,因此直接搜索即可。
即设 表示当前 先手,局面是 (即一个描述张牌在谁手中的状态,可以是一个数组,分别表示在高桥、青木或者桌子上,也可以是一个三进制的压缩状态),此时先手必赢/必输。
转移则枚举当前手的策略,即先花枚举将手里的哪张牌放到桌子上,再花 枚举 拿桌子上的哪张牌,然后更新局面状态,转移到下一个局面即可。
而当前局是先手必赢/必输,取决于下一个局面(注意下一个局面的先手是当前局面的后手)。因为是最优策略,因此下一个局面如果有(当前局面的后手)必输局面,那先手必赢,否则如果下一个局面全是(当前局面的后手)必赢局面,则先手必输。
因此求解是一个递归的过程。总的时间复杂度是
vector状态1700ms
#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, l; cin >> n >> m >> l; vector<int> c(n + m + l); for (auto& x : c) cin >> x; array<map<vector<int>, int>, 2> dp; vector<int> st(n + m + l); fill(st.begin(), st.begin() + n, 0); fill(st.begin() + n, st.begin() + n + m, 1); fill(st.begin() + n + m, st.end(), 2); auto dfs = [&](auto dfs, vector<int>& st, int p) -> bool { if (count(st.begin(), st.end(), p) == 0) { return dp[p][st] = 0; } if (dp[p].count(st)) return dp[p][st]; bool ok = true; for (int i = 0; i < n + m + l; i++) { if (st[i] == p) { st[i] = 2; for (int j = 0; j < n + m + l; j++) { if (st[j] == 2 && c[j] < c[i]) { st[j] = p; ok &= dfs(dfs, st, p ^ 1); st[j] = 2; } } ok &= dfs(dfs, st, p ^ 1); st[i] = p; } } return dp[p][st] = (ok ^ 1); }; if (dfs(dfs, st, 0)) cout << "Takahashi" << '\n'; else cout << "Aoki" << '\n'; return 0; }
三进制压缩状态200ms
#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, l; cin >> n >> m >> l; vector<int> c(n + m + l); for (auto& x : c) cin >> x; vector<int> base(n + m + l, 1); for (int i = 1; i < n + m + l; ++i) { base[i] = base[i - 1] * 3; } array<vector<int>, 2> dp{vector<int>(base.back() * 3, -1), vector<int>(base.back() * 3, -1)}; auto get_bit = [&](int x, int y) { return x / base[y] % 3; }; auto tr = [&](int& cur, int pos, int v) { cur -= get_bit(cur, pos) * base[pos]; cur += v * base[pos]; }; auto final = [&](int st, int p) { for (int i = 0; i < n + m + l; i++) { if (get_bit(st, i) == p) return false; } return true; }; auto dfs = [&](auto dfs, int st, int p) -> bool { if (final(st, p)) { return dp[p][st] = 0; } if (dp[p][st] != -1) return dp[p][st]; bool ok = true; for (int i = 0; i < n + m + l; i++) { if (get_bit(st, i) == p) { tr(st, i, 2); for (int j = 0; j < n + m + l; j++) { if (get_bit(st, j) == 2 && c[j] < c[i]) { tr(st, j, p); ok &= dfs(dfs, st, p ^ 1); tr(st, j, 2); } } ok &= dfs(dfs, st, p ^ 1); tr(st, i, p); } } return dp[p][st] = (ok ^ 1); }; int st = 0; for (int i = 0; i < n + m + l; i++) { tr(st, i, (i >= n) + (i >= n + m)); } if (dfs(dfs, st, 0)) cout << "Takahashi" << '\n'; else cout << "Aoki" << '\n'; return 0; }
G - Another Shuffle Window (abc380 G)
题目大意
给定一个关于的全排列和一个数字 。进行如下操作一次。
- 第一步,从随机选一个数
- 第二部,将 随机打乱
问进行操作后的逆序对的期望值。
解题思路
期望的求法就是该情况的逆序对数
发生该情况的概率
,对所有情况求和。因此我们先考虑所有情况怎么来的。
首先第一步,枚举,它数量只有 ,可以枚举。
枚举了 后,排列 就分成了三部分: ,其中 就是将会随机打乱的个数,然后就是左右两部分的数。
再然后就是第二部,将中间部分打乱,但这情况数有,显然不能枚举,我们考虑能否综合第二步的所有情况来求。
考虑逆序对的来源,根据的值,有这几部分:
- 部分
- 部分
- 部分
- 部分
- 部分
- 部分
除了最后一个,前个的逆序对的数量不会随着第二步的操作而改变。当第一个的变化时,我们可以实时维护前 部分的逆序对数量的变化。
而第六部分的 ,考虑里面的任意两个数 ,综合所有的随机打乱情况 ,谁前谁后其实各占一半,也就是说,任意一对数只有一半的概率会产生逆序对,而个数一共有 对数,每对形成逆序对的概率都是 ,因此第六部分的期望逆序对数量始终是 。
而前 部分的维护,即 最左边少一个数, 最右边多一个数, 最右边多一个数,最左边少一个数,依次计算这些数所产生的逆序对数量,然后更新即可。
而产生逆序对的数量,即对于数 ,我们想知道大于或小于 的数量,可以通过维护 部分的桶(即表示数字出现的次数 ),加以树状数组或线段树的单点修改和区间求和,从而在 的时间得到。即权值线段树或权值树状数组。
总的时间复杂度为。
神奇的代码
#include <bits/stdc++.h> using namespace std; using LL = long long; // starting from 1 template <typename T> class fenwick { public: vector<T> fenw; int n; int tot; fenwick(int _n) : n(_n) { fenw.resize(n); tot = 0; } inline int lowbit(int x) { return x & -x; } void modify(int x, T v) { tot += v; for (int i = x; i < n; i += lowbit(i)) { fenw[i] += v; } } T get(int x) { T v{}; for (int i = x; i > 0; i -= lowbit(i)) { v += fenw[i]; } return v; } }; const LL mo = 998244353; long long qpower(long long a, long long b) { long long qwq = 1; while (b) { if (b & 1) qwq = qwq * a % mo; a = a * a % mo; b >>= 1; } return qwq; } long long inv(long long x) { return qpower(x, mo - 2); } int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); int n, k; cin >> n >> k; vector<int> p(n); fenwick<int> l(n + 1), mid(n + 1), r(n + 1); for (auto& x : p) { cin >> x; } LL llc = 0, lmc = 0, mrc = 0, rrc = 0, lrc = 0; LL mmc = 1ll * k * (k - 1) % mo * inv(4) % mo; const int large = 1; const int small = 0; auto get_cnt = [&](fenwick<int>& f, int p, int large) { if (!large) return f.get(p - 1); else return f.tot - f.get(p); }; auto update_l = [&](int num, int v) { // the right of l l.modify(num, v); llc += get_cnt(l, num, large) * v; lmc += get_cnt(mid, num, small) * v; lrc += get_cnt(r, num, small) * v; }; auto update_m = [&](int num, int v) { mid.modify(num, v); lmc += get_cnt(l, num, large) * v; mrc += get_cnt(r, num, small) * v; }; auto update_r = [&](int num, int v) { // the left of r r.modify(num, v); lrc += get_cnt(l, num, large) * v; mrc += get_cnt(mid, num, large) * v; rrc += get_cnt(r, num, small) * v; }; for (int i = 0; i < k; ++i) update_m(p[i], 1); for (int i = n - 1; i >= k; --i) update_r(p[i], 1); LL ans = (llc + lmc + mrc + rrc + lrc + mmc) % mo; for (int i = 0; i < n - k; ++i) { update_m(p[i], -1); update_l(p[i], 1); update_r(p[i + k], -1); update_m(p[i + k], 1); ans = (ans + llc + lmc + mrc + rrc + lrc + mmc) % mo; } ans = ans * inv(n - k + 1) % mo; cout << ans << '\n'; return 0; }
本文作者:~Lanly~
本文链接:https://www.cnblogs.com/Lanly/p/18550426
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!