有一个长度为 N 的字符串 S,其中的每个字符要么是 B
,要么是 E
。
我们规定 S 的价值等于其中包含的子串 BB
以及子串 EE
的数量之和。
例如,BBBEEE
中包含 22 个 BB
以及 22 个 EE
,所以 BBBEEE
的价值等于 44。
我们想要计算 S 的价值,不幸的是,在我们得到 S 之前,约翰将其中的一些字符改为了 F
。
目前,我们只能看到改动后的字符串 S,对于其中的每个 F
,我们并不清楚它之前是 B
还是 E
。
请你计算,改动前的 S 有多少种可能的价值并将所有可能价值全部输出。
第一行包含一个整数 N
第二行包含改动后的字符串 S
第一行输出一个整数 K,表示改动前的 S 的可能价值的数量。
接下来 K 行,按照升序顺序,每行输出一个可能价值。
1≤N≤2×10^5
4
BEEF
2
1
2
9
FEBFEBFEB
2
2
3
10
BFFFFFEBFE
3
2
4
6
这道题没有模板可以套,所以我们要分类讨论。如果全部都是F的话证明价值从0-n都可以。如果不是就更具体讨论了。具体讨论如下(这里的low地点和high高点分别是最小价值和最高价值):
寻找第一个和最后一个非F字符的位置:
l
和 r
,分别初始化为字符串的开头和结尾。while
循环找到第一个非 ‘F’ 的字符的位置,分别赋给 l
,r
计算低点:
StringBuilder
对象 str
,将其初始化为输入字符串 s
。l
到 r
的范围内遍历字符串。str.charAt(i) == str.charAt(i - 1)
),则增加 low
计数。计算高点:
l
到 r
的范围内遍历字符串。str.charAt(i) == str.charAt(i - 1)
),则增加 high
计数。计算结束的范围和步长:
l
到 r
之间的字符个数(即 ends = l + n - 1 - r
)。ends
大于 0,说明字符串在两端有一些 ‘F’,需要计入 high
中,此时将 high
增加 ends
,并将步长 d
设置为 1。输出结果:
high - low
的结果除以步长 d
,并加上 1,得到最终输出的范围。low
到 high
按照步长 d
遍历,输出结果import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 读取输入
int n = scanner.nextInt();
String s = scanner.next();
if (s.equals(new String(new char[n]).replace('\0', 'F'))) {
// 如果全是F,输出整个范围
System.out.println(n);
for (int i = 0; i < n; i++) {
System.out.println(i);
}
} else {
// 寻找第一个和最后一个非F字符的位置
int l = 0, r = n - 1;
while (s.charAt(l) == 'F') l++;
while (s.charAt(r) == 'F') r--;
int low = 0, high = 0;
StringBuilder str = new StringBuilder(s);
// 计算低点
for (int i = l; i <= r; i++) {
if (str.charAt(i) == 'F') {
if (str.charAt(i - 1) == 'B') str.setCharAt(i, 'E');
else str.setCharAt(i, 'B');
}
if (i > l && str.charAt(i) == str.charAt(i - 1)) low++;
}
// 重置字符串
str = new StringBuilder(s);
// 计算高点
for (int i = l; i <= r; i++) {
if (str.charAt(i) == 'F') str.setCharAt(i, str.charAt(i - 1));
if (i > l && str.charAt(i) == str.charAt(i - 1)) high++;
}
// 计算结束的范围和步长
int ends = l + n - 1 - r, d = 2;
if (ends > 0) {
high += ends;
d = 1;
}
// 输出结果
System.out.println((high - low) / d + 1);
for (int i = low; i <= high; i += d)
System.out.println(i);
}
}
}
接着我们结合第一个测试用例来理解这个代码
输入样例1:
4
BEEF
输出样例1:
2
1
2
执行到这串代码的时候:
while (s.charAt(l) == 'F') {
l++;
}
while (s.charAt(r) == 'F') {
r--;
}
l还是0,r变为2
计算低点
执行到计算低点的代码时候,当i=2时,进来low++,所以low=1
然后执行到i=3时,因为已经把F替换为跟前面一个字符不同的字符,所以进不到low++的判断
计算高点
计算ends
high
时,我们希望每个 ‘F’ 能够单独计数,因此需要考虑两端的 ‘F’ 个数输出:
(high - low) / d + 1
的计算是为了确定最终输出的分组数量d
表示每次增加的索引步长,保证了每个 ‘F’ 只能被计数一次