本文是 AtCoder Beginner Conteset(ABC)386 的总结笔记。
A - Full House 2
Problem:A - Full House 2
模拟
有 ABCD 写着数字的四张牌。问能否添加一张牌,使得五张牌构成:3 张 x 和 2 张 y 的形式。
思路非常简单。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
|
#include <bits/stdc++.h> using namespace std; typedef long long LL; typedef pair<int, int> PII;
int a[4]; map<int, int> h;
void solve() { for (int i = 0; i < 4; i++) { cin >> a[i]; h[a[i]]++; }
sort(a, a + 4);
if ((h[a[0]] == 2 && h[a[3]] == 2) || (h[a[0]] == 3 && h[a[3]] == 1) || (h[a[0]] == 1 && h[a[3]] == 3)) cout << "Yes" << endl; else cout << "No" << endl; }
int main() { cin.tie(0); ios_base::sync_with_stdio(false); solve(); return 0; }
|
B - Calculator
Problem:B - Calculator
模拟
这道题目其实很暴力。只需要将所有的 00
转换成一个不相关的字符,最后看一下总长度就知道需要按多少次按键了。
本答案是将所有的 00
都转化为 1
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
#include <bits/stdc++.h> using namespace std; typedef long long LL; typedef pair<int, int> PII;
string s;
void solve() { cin >> s;
while (s.find("00") != s.npos) s.replace(s.find("00"), 2, "1");
cout << s.size() << endl; }
int main() { cin.tie(0); ios_base::sync_with_stdio(false); solve(); return 0; }
|
C - Operate 1
Problem:C - Operate 1
模拟
题目:
有三种操作:
- 给 S 中添加任意一个字符
- 删除 S 中的任意一个字符
- 选择 S 中的任意一个字符并转化为另一个字符
请判断执行次操作中,是否能将 S 转化为 T
约束条件:
字符串最大长度
思路:
本题目是 F 题在情况下的特例。
分成三种情况来讨论:
case1:S 与 T 长度相同
case2:S 比 T 长度短 1
case3:S 比 T 长度长 1
case1 处理方法:
找到正向和反向的最后一个差异点,如果位置相同,说明只有一个差异点
case2 和 case3 处理方法:
只看长的那个字符串。找到第一个差异点,判断一下去掉这个差异点的字符串s 和 t 是否相同即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
|
#include <bits/stdc++.h> using namespace std; typedef long long LL; typedef pair<int, int> PII;
string s, t; int k;
void solve() { cin >> k >> s >> t;
if (s == t) { cout << "Yes" << endl; return; }
if (s.size() == t.size()) { int x, y; for (int i = 0; i < s.size(); i++) if (s[i] != t[i]) x = i; for (int i = s.size() - 1; i >= 0; i--) if (s[i] != t[i]) y = i; if (x == y) cout << "Yes" << endl; else cout << "No" << endl; return; }
if (s.size() < t.size()) swap(s, t);
if (s.size() - t.size() == 1) { int x; for (int i = 0; i < s.size(); i++) if (s[i] != t[i]) { x = i; break; }
string tmp = s.substr(0, x) + s.substr(x + 1);
if (tmp == t) cout << "Yes" << endl; else cout << "No" << endl; return; }
cout << "No" << endl; }
int main() { cin.tie(0); ios_base::sync_with_stdio(false); solve(); return 0; }
|
D - Diagonal Separation
Problem:D - Diagonal Separation
贪心
题目:
给定一个的网格,希望能按照下面条件将格子涂成黑色或白色:
- 对于每一行:左边的格子都是黑色,右边的格子都是白色
- 对于每一列:上面的格子都是黑色,下面的格子都是白色
已经给定了个格子的颜色,请问是否可以将剩余的个格子涂色,以满足上面条件。
约束条件:
思路:
如果所有的点都是合法的,我们可以很容易的发现单调性:
从上往下,最左侧的白色格子出现的位置 min_y
一定是逐渐减小的。
每一行中,一旦要是有任何一个黑色格子的 y 坐标大于 min_y
,就一定不能满足题目的约束条件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
|
#include <bits/stdc++.h> using namespace std; typedef long long LL; typedef pair<int, int> PII;
const int N = 2e5 + 10;
int n, m; struct Node { int x, y; char c; } p[N];
bool cmp(const Node& a, const Node& b) { if (a.x == b.x) return a.y < b.y; return a.x < b.x; }
void solve() { cin >> n >> m; for (int i = 0; i < m; i++) cin >> p[i].x >> p[i].y >> p[i].c;
sort(p, p + m, cmp);
int min_y = 1e9 + 10; for (int i = 0; i < m; i++) { if (p[i].c == 'W') min_y = min(min_y, p[i].y); else if (p[i].y >= min_y) { cout << "No" << endl; return; } } cout << "Yes" << endl; }
int main() { cin.tie(0); ios_base::sync_with_stdio(false); solve(); return 0; }
|
E - Maximize XOR
Problem:E - Maximize XOR
DFS + 优化
题目:
给定一个长度为的非负整数序列,整数。
保证组合数
从中选择个不同的元素,求他们值的最大值。
备注:
:相异为,相同为。例如:
约束条件:
思路:
这道题目直接使用 DFS 搜索。但是需要两处前置知识:
前置知识 1:XOR 的性质
XOR 有一些重要的性质:
- 可逆性:如果,那么,也就是说再次 XOR 同一个数就相当于把它 “消去”
- 自我抵消:任何数字和他本身做 XOR,结果都是0,即
- 恒等性:热河数字 a 和 0 做 XOR,结果不变,即
从上面的性质,可以推出本题中用到的结论:
假设长度为 N 的数列,取出了前 K 个元素做 XOR 运算。
等价于先把所有元素做 XOR 运算,再和没有取出的 N-K 个元素做 XOR 运算。
前置知识 2:组合数的性质
组合数有一个重要的性质:
显然,从 n 个物品里面选 m 个组成的方案数,和从 n 个物品中选 n-m 个丢掉的方案数是一致的。
本题思路:
根据上述的两条性质,我们可以对 DFS 进行优化:
- 如果k的值比较小,我们就搜即可
- 如果 k 的值比较大,我们就搜,但是前提要先将所有数组 a 的值进行一遍 XOR 运算。
为什么要使用这个优化方法呢?
本题中的递归函数 dfs,每调用一次我可以视为的时间复杂度。当 k 很大的时候,递归函数总的调用次数为次,尽管的值可能并不大,但是的值会非常的大。
举例:尽管 $C^{98}{100} = 4950 < 10^{6}C^{50}{100} = 10^{29}$ 非常的巨大,显然超出了限定的时间复杂度。
所以使用这个优化方法可以极大的减少时间复杂度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
|
#include <bits/stdc++.h> using namespace std; typedef long long LL; typedef pair<int, int> PII;
const int N = 2e5 + 10;
LL res; LL a[N]; int n, k;
void dfs(int x, LL sum, int n, int k) { if (k == 0) { res = max(res, sum); return; } if (x == n + 1) return;
dfs(x + 1, sum ^ a[x], n, k - 1); dfs(x + 1, sum, n, k); }
void solve() { cin >> n >> k; for (int i = 1; i <= n; i++) cin >> a[i];
LL sum = 0; if (k > n / 2) { k = n - k; for (int i = 1; i <= n; i++) sum ^= a[i]; }
dfs(1, sum, n, k);
cout << res << endl; }
int main() { cin.tie(0); ios_base::sync_with_stdio(false); solve(); return 0; }
|
F - Operate K
Problem:F - Operate K
最短编辑距离 + 带状 DP
题目:
有三种操作:
- 给 S 中添加任意一个字符
- 删除 S 中的任意一个字符
- 选择 S 中的任意一个字符并转化为另一个字符
请判断执行次操作中,是否能将 S 转化为 T
约束条件:
字符串最大长度
思路:
本题是 C 的加强版。
直观上,这是一道“最短编辑距离”问题。
前置知识:“最短编辑距离”问题的标准做法:
这类问题有一个标准的 DP 做法如下:
令表示字符串的前个字符变成的前字符所需要的最短编辑距离。
初始状态,那么 i 和 j 的双重循环下,状态迁移公式为:
其中:
- :表示删除 S 的一个字符
- :表示给 S 插入一个字符(或者说给 T 删去一个字符)
- :表示“替换”或“匹配”。如果,则无需替换,;否则
总时间复杂度为:
本题的优化思路:带状区域计算 DP
如果按照标准做法,那本题的时间复杂度是,显然会 TLE。
但是本题规定了最大操作数不超过(不超过 20 次操作),这个约束条件就带来了优化空间。
即:只对“有希望编辑距离不超过” 的位置进行 DP 计算。其他位置将会直接认为距离非常大(用 ∞ 表示),从而剪枝掉大量不必要的计算。
如何做?
在计算的时候,如果的差距很大,意味着比短或长很多。如果要将变得和完全相同,则操作次数至少要大于。此时就可以将超过的区域直接视为 +∞,不再计算。
具体做法:
这样就将原本的时间复杂度压缩到
注意点:
习惯上可能直接就把 f 数组声明为大小。但是本题中最大为 5e5
,此时开出的数组空间为:5e5 * 5e5 * sizeof(int)
大小的内存。对于 int
类型(假设为 4 字节大小),这个数组将会消耗 1e12 byte = 1000GB
的空间,显然是不现实的。如果直接声明这么大的数组,会报 bus error
错误。
而且我们实际上只使用了中间很小的一部分,绝大多数的空间都是用不上的。所以使用偏置换算的方法即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
|
#include <bits/stdc++.h> using namespace std; typedef long long LL; typedef pair<int, int> PII;
const int N = 5e5 + 10;
string S, T; int K; int f[N][60];
int get_dp(int i, int j) { if (abs(i - j) > K) return 0x3f3f3f3f; return f[i][j - i + 30]; }
void solve() { cin >> K >> S >> T; int slen = S.size(), tlen = T.size();
S = " " + S; T = " " + T;
memset(f, 0x3f, sizeof f);
f[0][30] = 0; for (int i = 1; i <= K; i++) f[0][30 + i] = i; for (int i = 1; i <= K; i++) f[i][30 - i] = i;
for (int i = 1; i <= slen; i++) { for (int x = 0; x < 60; x++) {
int j = i - 30 + x; if (j <= 0 || j > tlen) continue;
int cur_dp = 0x3f3f3f3f; cur_dp = min(cur_dp, get_dp(i - 1, j) + 1); cur_dp = min(cur_dp, get_dp(i, j - 1) + 1);
int c = 1; if (S[i] == T[j]) c = 0; cur_dp = min(cur_dp, get_dp(i - 1, j - 1) + c); f[i][x] = cur_dp; } }
if (get_dp(slen, tlen) <= K) cout << "Yes" << endl; else cout << "No" << endl; }
int main() { cin.tie(0); ios_base::sync_with_stdio(false); solve(); return 0; }
|