AtCoder Beginner Contest 381
省流版
- A. 按题意判断即可
- B. 按题意判断即可
- C. 枚举
/
的位置,然后分别向左右找到最长的1
串和2
串,然后取最小值即可 - D. 讨论起始位置的奇偶性,然后用双指针,每两个字符每两个字符,维护出现的次数为2,两种情况取最大值即可
- E. 答案为所有
/
的左右12
个数的最小值的最大值,注意到个数随着/
的增大是单调的,而最大值出现在交点,二分找该交点即可 - F. 表示已经选择的字符的集合状态是时的最小的选择字符的位置,转移枚举下一个字符,然后找到下一个字符的位置,然后更新即可
A - 11/22 String (abc381 A)
题目大意
给定一个字符串,问它是不是形如11/22
的形式。即一个1
串和一个2
串,长度相等,中间用/
分隔。
解题思路
找到/
,然后判断左右两边是否是1
串和2
串即可。
神奇的代码
n = input() s = input() s = s.split('/') if len(s) == 2 and len(s[0]) == len(s[1]) and all(i == '1' for i in s[0]) and all(i == '2' for i in s[1]): print('Yes') else: print('No')
B - 1122 String (abc381 B)
题目大意
给定一个字符串,问它是不是形如11223344
的形式。即每两位相同,且每个字符出现的次数为2。
解题思路
判断奇数位和偶数位是否相同,然后判断每个字符出现的次数是否为2即可。
神奇的代码
s = input() c = set(s) if s[::2] == s[1::2] and all(sum(1 for i in s if i == j) == 2 for j in c): print('Yes') else: print('No')
C - 11/22 Substring (abc381 C)
题目大意
给定一个字符串,问它的最长子串是多少,使得子串中1
的个数和2
的个数相等,中间用/
分隔。
解题思路
枚举/
的位置,然后分别向左右找到最长的1
串和2
串,然后取最小值即可。
因为每个子串只包含一个/
,因此字符串就被/
分成若干部分,每个部分最多只会遍历两次,因此时间复杂度为。
神奇的代码
#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; string s; cin >> n >> s; int ans = 0; auto find1 = [&](int x) { for (int i = x; i >= 0; --i) { if (s[i] != '1') return x - i; } return x + 1; }; auto find2 = [&](int x) { for (int i = x; i < n; ++i) { if (s[i] != '2') return i - x; } return n - x; }; for (int i = 0; i < n; i++) { if (s[i] == '/') { ans = max(ans, min(find1(i - 1), find2(i + 1))); } } ans = ans * 2 + 1; cout << ans << '\n'; return 0; }
D - 1122 Substring (abc381 D)
题目大意
给定一个字符串,问它的最长子串是多少,使得每两位相同,且每个字符出现的次数为2。
解题思路
如果每两个看成一个字符,问题其实就是求最长的子串,其每个字符出现的次数为1。这是一个经典的双指针问题,即维护一个区间,使得区间内的每个字符出现的次数为1,然后不断向右移动,直到区间内的字符出现的次数不满足条件,然后向右移动,直到区间内的字符出现的次数满足条件,然后继续向右移动,直到字符串结束。因为每个字符最多只会被访问两次,因此时间复杂度为。
而此处的字符是两个字符,其实也没什么区别,原本是一个字符一个字符的移动,现在是两个字符两个字符的移动而已。区别仅仅是起始位置的奇偶性,即可以从第一个字符开始,也可以从第二个字符开始。
这两种情况分别讨论,然后取最大值即可。
神奇的代码
#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; --i; } auto solve = [&](int st) { int ret = 0; int la = st; vector<int> cnt(n); for (int i = st; i < n; i += 2) { if (i + 1 >= n || a[i] != a[i + 1] || cnt[a[i]] == 1) { ret = max(ret, i - la); while (la < i && (a[i] != a[i + 1] || cnt[a[i]] == 1)) { cnt[a[la]]--; la += 2; } } if (a[i] == a[i + 1]) cnt[a[i]]++; else la += 2; }; ret = max(ret, n - la - ((n - st) & 1)); return ret; }; int ans = max(solve(0), solve(1)); cout << ans << '\n'; return 0; }
E - 11/22 Subsequence (abc381 E)
题目大意
给定一个字符串,给定个询问,每个询问给定,问的最长子序列,使得子序列中1
的个数和2
的个数相等,中间用/
分隔。
解题思路
对于每个询问,枚举/
的位置,答案就是的的个数和的的个数的最小值的两倍加一。后者个数的计算可以通过前缀和来实现。但前者枚举的复杂度是,因此总的复杂度是,无法通过。
注意答案是,其中表示的的个数,表示的的个数。随着的增大,是单调递增的,是单调递减的。
因此两者最小值,首先是取在,然后再变成在,而其最大值就在这两者的交点。因此可以通过二分来找到这个交点。这样就可以将复杂度降到。
虽然这个是个单峰函数,但由于有相同的值,不能直接三分找到最大值。
神奇的代码
#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; array<vector<int>, 2> cnt{vector<int>(n), vector<int>(n)}; vector<int> pos; for (int i = 0; i < n; i++) { if (s[i] == '/') pos.push_back(i); else cnt[s[i] - '1'][i] = 1; } partial_sum(cnt[0].begin(), cnt[0].end(), cnt[0].begin()); partial_sum(cnt[1].begin(), cnt[1].end(), cnt[1].begin()); auto get_sum = [&](int l, int r, int c) { return cnt[c][r] - (l - 1 < 0 ? 0 : cnt[c][l - 1]); }; auto solve = [&](int L, int R) { auto calc1 = [&](int x) { return get_sum(L, x, 0); }; auto calc2 = [&](int x) { return get_sum(x, R, 1); }; auto check = [&](int x) { return calc1(x) <= calc2(x); }; int l = lower_bound(pos.begin(), pos.end(), L) - pos.begin(); int r = upper_bound(pos.begin(), pos.end(), R) - pos.begin(); if (l == r) return 0; int ret = 0; while (l + 1 < r) { int m = (l + r) / 2; if (check(pos[m])) l = m; else r = m; } for (int i = l; i <= r; i++) { if (L <= pos[i] && pos[i] <= R) ret = max(ret, min(calc1(pos[i]), calc2(pos[i]))); } return ret * 2 + 1; }; while (q--) { int l, r; cin >> l >> r; --l, --r; int ans = solve(l, r); cout << ans << '\n'; } return 0; }
F - 1122 Subsequence (abc381 F)
题目大意
给定一个字符串,问它的最长子序列是多少,使得每两位相同,且每个字符出现的次数为2。
解题思路
注意字符只有种。一种朴素搜索就是花枚举字符出现的顺序
,一旦顺序确定好了,就是可行性判断,找出这个子序列就可以用贪心的方法,即每次找到一个字符,然后找到下一个字符,直到找到所有的字符。这样的复杂度是的。
考虑如何优化呢,找到其中重复的部分,比如考虑排列123 4567
以及213 4567
以及312 4567
,我们要依次判断这些排列的可行性,比如我们找到4
,就要从上一次的位置开始找,对于123
、213
、312
,这所谓的上一次的位置
可能是不一样的,但因为我们要找4
,自然希望上一次位置
越靠前越好。而对于我们找4
还是找5
,取决于我们之前是否选择了4
。因此这里可以把排列的信息压缩掉,即记表示已经选择的字符的集合状态是时的最小的选择字符的位置。比如状态表示选择了123
,那就是选择顺序123, 132, 213, 231, 312, 321
按照上述贪心方法找到的子序列最大下标的最小值。
转移就是枚举下一个字符,然后找到下一个字符的位置,然后更新即可。时间复杂度是的。
神奇的代码
#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; constexpr int num = 20; array<vector<int>, num> pos; for (int i = 0; i < n; i++) { int a; cin >> a; --a; pos[a].push_back(i); } constexpr int up = (1 << num); vector<int> dp(up, n); dp[0] = 0; int ans = 0; for (int i = 0; i < up; ++i) { for (int j = 0; j < num; ++j) { if ((i >> j) & 1) { int la = i ^ (1 << j); auto it = lower_bound(pos[j].begin(), pos[j].end(), dp[la]); if (it != pos[j].end() && next(it) != pos[j].end()) { dp[i] = min(dp[i], *next(it)); ans = max(ans, __builtin_popcount(i)); } } } } cout << ans * 2 << '\n'; return 0; }
G - Fibonacci Product (abc381 G)
题目大意
定义。
求
解题思路
<++>
神奇的代码
本文作者:~Lanly~
本文链接:https://www.cnblogs.com/Lanly/p/18568552
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
2023-11-25 AtCoder Beginner Contest 330