第二种操作是从 $1$ 开始修改的,也就是修改一段前缀,考虑递推。由于操作可逆,所以我们可以转换为将读入的串变为全 A 串所需的最小变换次数。

设 $f_{i,0}$ 表示前 $i$ 个字符均为 A 时的最小变换次数,设 $f_{i,1}$ 表示前 $i$ 个字符均为 B 时的最小变换次数。在处理第 $i$ 个字符时,前 $i - 1$ 个字符只有全 A 或全 B 两种情况,而我们现在可以将前 $i$ 个字符变为全 A 或全 B。令第 $i$ 个字符是 A,进行分类讨论:

  • $f_{i - 1,0} \to f_{i,0}$ 不需要操作,直接在末尾添上 A

  • $f_{i - 1,0} \to f_{i,1}$ 添上 A 后将区间 $[1,i]$ 进行翻转。

  • $f_{i - 1,1} \to f_{i,0}$ 将区间 $[1,i - 1]$ 进行翻转,之后添上 A

  • $f_{i - 1,1} \to f_{i,1}$ 将区间 $[1,i - 1]$ 进行翻转,之后将 A 变为 B 并添上。操作等价于 $f_{i,0} \to f_{i,1}$。

若第 $i$ 个字符是 B 同理。最后的要求全为 A 串,答案也就是 $\min (f_{n,0},f_{n,1} + 1)$。

由于 $f_{i,0},f_{i,1}$ 只与 $f_{i - 1,0},f_{i - 1,1}$ 有关,所以我们可以将 $f$ 数组的第一维滚掉,即空间上的一个优化。

最后代码如下:

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
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#define init(x) memset (x,0,sizeof (x))
#define ll long long
#define ull unsigned long long
#define INF 0x3f3f3f3f
using namespace std;
const int MAX = 1e6 + 5;
const int MOD = 1e9 + 7;
inline int read ();
int n,f[2][2];
int main ()
{
//freopen (".in","r",stdin);
//freopen (".out","w",stdout);
n = read ();
for (int i = 1;i <= n;++i)
{
char ch;scanf ("%c",&ch);
if (ch == 'A') f[1][0] = min (f[0][0],f[0][1] + 1),f[1][1] = min (f[1][0],f[0][1]) + 1;//f[1][0] 之前已经算出,直接使用
else f[1][1] = min (f[0][1],f[0][0] + 1),f[1][0] = min (f[1][1],f[0][0]) + 1;
f[0][0] = f[1][0],f[0][1] = f[1][1];
}
printf ("%d\n",min (f[0][0],f[0][1] + 1));
return 0;
}
inline int read ()
{
int s = 0;int f = 1;
char ch = getchar ();
while ((ch < '0' || ch > '9') && ch != EOF)
{
if (ch == '-') f = -1;
ch = getchar ();
}
while (ch >= '0' && ch <= '9')
{
s = s * 10 + ch - '0';
ch = getchar ();
}
return s * f;
}