Acwing4993. FEB详细题解
这篇题解是在大佬题解基础上衍生一些自己的理解,传送门:AcWing 4993. FEB(贪心+分类讨论) - AcWing
题目
有一个长度为 N 的字符串 S,其中的每个字符要么是 B,要么是 E。
我们规定 S 的价值等于其中包含的子串 BB 以及子串 EE 的数量之和。
例如,BBBEEE 中包含 22 个 BB 以及 22 个 EE,所以 BBBEEE 的价值等于 4。
我们想要计算 S 的价值,不幸的是,在我们得到 S 之前,约翰将其中的一些字符改为了 F。
目前,我们只能看到改动后的字符串 S,对于其中的每个 F,我们并不清楚它之前是 B 还是 E。
请你计算,改动前的 S 有多少种可能的价值并将所有可能价值全部输出。
input1:
4
BEEF
output1:
2
1
2
input2:
9
FEBFEBFEB
output2:
2
2
3
input3:
10
BFFFFFEBFE
output3:
10
BFFFFFEBFE
结论:
每个字符串的值都可以分为三段来求;并且值是有规律的等差数列,所以我们可以求出最大值max_x和最小值min_x以及公差(1或2)来算出S可能价值的数量
前缀都是F的一段 + 中间一段 + 后缀都是F的一段
比如:FFF + BFBFB + FFF
最小值 = 前缀的最小值(等于0) + 中间一段的最小值 (取决于F)+ 后缀的最小值(等于0)
很明显形如FFF的前缀的最小值是0(实际上所有的前缀最小值都是0),取EBE,而不取BEB
同理,后缀的最小值也为0,取EBE
最大值 = 前缀的max(等于前缀中F的个数) + 中间一段的最大值(取决于F) + 后缀的max(等于后缀中F的个数)
形如FFF的前缀的max值是:3(而不是2),因为FFFB…取max——>BBBB…,即前缀的max(等于前缀中F的个数)
同理,后缀的max =后缀的max(等于后缀中F的个数)
上面没看懂没关系,下面会解释
详解:
我们先列举几个F在字符串中的情况:
1.F在首尾位置:FB…EF
对答案的贡献值只有0或者1,就是与相邻字母相同或者不同
2.F不在首尾位置,有两个相邻字母:EFB或者BFB
EFB:F两侧字母不同,F对于答案的贡献值只有1
BFB:F两侧字母相同,F对于答案的贡献值为0或2
ps:我们这里不需要考虑FFB或者BFF这种情况,F的状态最终一定会被确定,一定可以归纳在情况2中:有两个相邻字母
等差数列:
结合上面内容,我们可以想到,如果首尾没有F,中间的F对答案的贡献值不是0就是2,可以想象成一排开关,打开加2分,关上加0分,你可以自由选择开关,此时相当于对于F的贡献值进行排列组合,最终结果就是以2为公差的等差数列(这个开关的过程其实就是你在尝试找回被约翰打乱前的开关状态,F这个状态就是一种中间态)
但是如果首尾有F呢?,那么首尾F对于答案的贡献可能多一个1,也就是说新增一个或者两个开关加入到你的选择中,此时最终的结果就是以1为公差的等差数列(我们要明白,虽然我们自由选择开关的分数是随机的,但是我们列举出所有的排列组合所获得的分数有序排列得到的是一个等差数列)
最大值和最小值(数列的首项和末项)
前缀后缀的max和min前面写了,如果不理解,写几个样例模拟一下就好了
重点是中间部分的max和min,比如FFF + BFBFB + FFF
BFBFB的最小值可以从左到右进行枚举,将每个F替换成与上一个位置不相同的字母
同理最大贡献值即从左到右进行枚举,将每个F替换成与上一个位置相同的字母
具体怎么操作看代码就好,代码已经分好块了
#include<bits/stdc++.h>
using namespace std;
int n;
int main()
{
cin >> n;
string s,str;
cin >> s;
str = s;
int l=0,r=n-1;
while(l < n && s[l] == 'F') l++;//l是前缀F的数量
while(n >= 0 && s[r] =='F') r--;//n-1-r是后缀F的数量
int min_x = 0,max_x = 0;
int d = 2 - (s[0] == 'F' || s[n-1] == 'F');//求公差
//求中间段的最小值,以l和r为边界
//1.先按照最小值原则,将F变为B或E:F和前一个字母一定不一样
for(int i=l;i <= r;i++)
{
if(s[i] == 'F')
{
if(s[i - 1] == 'E')
s[i] = 'B';
else
s[i] = 'E';
}
}
//2.求值
for(int i=l;i <= r-1;i++)//注意这里i <= i-1,而不是<= i,如果是后者那么下面的s[i+1]就越界到后缀了
{
if(s[i] == s[i+1])
min_x++;
}
//求中间段的最大值,这里用的是字符串str别再用s了
//1.将F变为B或E:F和前一个字母一定一样
for(int i=l;i <= r;i++)
{
if(str[i]=='F')
{
str[i] = str[i-1];
}
}
//2.求值
for(int i=l;i <= r-1;i++)
{
if(str[i]==str[i+1])
max_x++;
}
//考虑特殊情况:全为F,那么最大数量就为n-1
if(l==n) l--;//看第13行:l是前缀F的数量。所以这里的l要减1
max_x += l;
//加上后缀中F的个数,如果全为F,那么前缀后缀重复了,只需要加前缀
if(l <= r) max_x += n - 1 - r;//l=r,可以理解为FFBFF,l和r都指向中间那个B
printf("%d %d\n",max_x,min_x);
printf("%d\n",(max_x - min_x) / d + 1);//等差数列的项数公式为:项数 = (末项 - 首项) / 公差 + 1
for(int i = min_x;i <= max_x;i += d)
printf("%d\n",i);
return 0;
}