Loading

2022.10.25 总结

1.逐月 P4976

逐月 P4976

题意

Farmer John 有 \(n\) 头奶牛,每头奶牛要么是更赛牛(G),要么是荷斯坦牛(H)。

Farmer John 希望在他的 \(n\) 头奶牛中,站在偶数位置的更赛牛最多。

他的每次操作可以选择奶牛序列的一个偶数长的前缀并翻转。

请你求出 Farmer John 最少需要多少次操作能使站在偶数位置的更赛牛最多。

思路

100 分

首先,我们先思考为什么要选择一个偶数长的前缀?

答案很简单,如果是一个奇数长的前缀,那么翻转后每头奶牛下标的奇偶性就不会改变了,那么翻转就没有用了。

而且,因为翻转是都是翻转偶数长的前缀,所以可以把两头奶牛分为一组求解。


那么每组奶牛就有 \(4\) 种情况了:

  1. HH

  2. GG

  3. GH

  4. 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

逐月 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;
}
posted @ 2023-03-02 22:45  chengning0909  阅读(18)  评论(0编辑  收藏  举报