2022.10.25 总结
1.逐月 P4976
题意
Farmer John 有 \(n\) 头奶牛,每头奶牛要么是更赛牛(G),要么是荷斯坦牛(H)。
Farmer John 希望在他的 \(n\) 头奶牛中,站在偶数位置的更赛牛最多。
他的每次操作可以选择奶牛序列的一个偶数长的前缀并翻转。
请你求出 Farmer John 最少需要多少次操作能使站在偶数位置的更赛牛最多。
思路
100 分
首先,我们先思考为什么要选择一个偶数长的前缀?
答案很简单,如果是一个奇数长的前缀,那么翻转后每头奶牛下标的奇偶性就不会改变了,那么翻转就没有用了。
而且,因为翻转是都是翻转偶数长的前缀,所以可以把两头奶牛分为一组求解。
那么每组奶牛就有 \(4\) 种情况了:
-
HH
-
GG
-
GH
-
HG
先看第一种,是 HH。因为最后答案只和 G 有关,所以可以不用考虑。
再看第二种,GG。你会发现,两个 G 在翻转之前,下标一定是一个奇数和一个偶数,而翻转后也是一个奇数和一个偶数,相当于没有翻转,也不用考虑。
而对于第三种和第四种,翻转后是会改变下标奇偶性的,所以需要考虑。
那么既然只有 GH 和 HG 两种情况了,那不如将 GH 变成 0,HG 变成 1。
假设有一个序列是 GHGHHG,就可以简化成 001。
而这个序列最终应变为 111,因为在 HG 中,G 在偶数位置上面。
所以,这个题就变成了:将一个 01 序列变成全 1 的序列,问最少要翻转多少次。
这个题目就变得和 洛谷 P2708 硬币翻转 一样了。
现在,又有一个问题了,硬币翻转要怎么求解?
先来推一个样例:00110001
首先,将前两个 0 翻转成 1,序列变成了 11110001
然后,把前四个 1 翻转成 0,也就是 00000001
最后,把所有 0 翻成 1,序列就成了 11111111
你会发现,每次翻转都是翻的连续的一段,并且这一段的数字相同。
所以,直接模拟就可以了。
但是,还有一个小细节需要注意,最后一个字符有可能是 0,所以需要再判断一次。
比如说:0011000。
先翻转前两个,1111000。
再翻转前四个,0000000。
最后全部翻转,1111111。
时间复杂度
枚举整个序列,\(O(n)\)。
空间复杂度
记录长度为 \(n\) 的序列,\(O(n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
int n, cnt;
string s;
char l;
int main() {
freopen("photoshoot.in", "r", stdin);
freopen("photoshoot.out", "w", stdout);
cin >> n >> s;
for (int i = 0; i < n; i += 2) {
if (s[i] != s[i + 1]) { // 是 HG 或者 GH
cnt += l && s[i] != l; // 判断是否是连续的一段
l = s[i]; // 更新
// 因为 GH 和 HG 的首字母不同,可以只记录首字母
}
}
cout << cnt + (l == 'G'); // 如果最后一个是 GH
return 0;
}
2. 逐月 P4978
题意
奶牛 Bessie 在学习转化金属。对于 \(1 \le i \le n \le 100\),她有 \(a_i\) 块金属 \(i\)。
她还知道 \(k\) \((1 \le k < n)\) 个配方,在每个配方中,她可以融合 \(b_{i, 1}, b_{i, 2} \dots b_{i, m_i}\)各一个单位,成为一个单位的金属 \(l_i\)。
数据保证,对于每个 \(l_i\),它是大于所有 \(b_{i, j}\) 的,并且每种金属只有一种融合方法。
请你求出 Bessie 在融合很多次后,最多能得到多少块金属 \(n\)。
思路
数据范围
测试点 \(2\) 中,对于 \(1 \le i < n\),一单位金属 \(i\) 可以被转化为一单位金属 \(i + 1\)。
测试点 \(3 - 4\) 中,每个配方均将一单位的一种金属转化为另一种金属。
测试点 \(5 - 11\) 没有额外限制。
9 分 ~ 10 分
由于题目说了,测试点 \(2\) 中,对于 \(1 \le i < n\),一单位金属 \(i\) 可以被转化为一单位金属 \(i + 1\),所以可以得到:
\(a_2 = a_2 + a_1, a_3 = a_3 + a_2 \dots a_n = a_n + a_{n - 1}\)。
直接将所有 \(a_i\) 的和相加即可。
27 分 ~ 45 分
在题目的数据范围中,第二条是:测试点 \(3 - 4\) 中,每个配方均将一单位的一种金属转化为另一种金属,也就是说,可以直接递推求解。
100 分
我们可以将金属看做点,配方看做线,所以如果是下面这个样例的话:
5
2 0 0 1 0
3
5 2 3 4
2 1 1
3 1 2
4 2 2 3
也就是说,要想得到一个 5,得先得到一个 3和一个 4,而要想得到一个 3 和一个 4,得先得到三个 2,依此类推。
所以,可以使用分治求解。
时间复杂度
分治,\(O(n ^ 2)\)。
枚举融合成一个金属 \(n\),\(O(n \times \max\{m_i\})\)。
总时间复杂度为 \(O(n ^ 3 \times \max\{m_i\})\)。(居然能过?)
空间复杂度
数组记录配方,数量,\(O(n \times m)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 110, M = 110;
int n, a[N], k, m[N], b[N][M], cnt;
bool l[N];
bool F(int t) {
if (a[t]) {
a[t]--;
return 1;
}
if (!l[t]) {
return 0;
}
bool flag = 1;
for (int i = 1; i <= m[t]; i++) {
flag &= F(b[t][i]);
}
return flag;
}
int main() {
freopen("alchemy.in", "r", stdin);
freopen("alchemy.out", "w", stdout);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
cin >> k;
for (int i = 1; i <= k; i++) {
int p;
cin >> p;
l[p] = 1;
cin >> m[p];
for (int j = 1; j <= m[p]; j++) {
cin >> b[p][j];
}
}
while (F(n)) {
cnt++;
}
cout << cnt + a[n];
return 0;
}