题意
咕咕东很聪明,但他最近不幸被来自宇宙的宇宙射线击中,遭到了降智打击,他的英语水平被归零了!这一切的始作俑者宇宙狗却毫不知情!
此时咕咕东碰到了一个好心人——TT,TT在吸猫之余教咕咕东学英语。今天TT打算教咕咕东字母A 和字母B,TT给了咕咕东一个只有大写A、B组成的序列,让咕咕东分辨这些字母。
但是咕咕东的其他学科水平都还在,敏锐的咕咕东想出一个问题考考TT:咕咕东问TT这个字符串 有多少个子串是Delicious的。
TT虽然会做这个问题,但是他吸完猫发现辉夜大小姐更新了,不想回答这个问题,并抛给了你, 你能帮他解决这个问题吗?
Delicious定义:对于一个字符串,我们认为它是Delicious的当且仅当它的每一个字符都属于一个 大于1的回文子串中。
Input
输入第一行一个正整数n,表示字符串长度 接下来一行,一个长度为n只由大写字母A、B构成的字符串。
Output
输出仅一行,表示符合题目要求的子串的个数。
输入样例
5
AABBB
输出样例
6
提示
分析
他来了他来了,他带着大范围数据走来了【溜掉】
又是一道规律分析题,但是要复杂一点。
- 题目分析&解决方法
1. 题意
题目中的关键定义有点不好懂噶,大概意思就是要我们求一个子序列,它要满足:
- 序列中每一个元素都在一个回文子串中
- 所有元素所在的回文子串都是该序列的子序列
- 回文子串的长度大于1
这个序列的特点是:只有A和B两个元素,这一点也将利于之后的解题。
其中,回文子串指的就是顺序逆序读出来的内容都完全一样的序列。
2. 方法
- 规律分析
对不同长度的子序列进行枚举,可以发现以下特点:
1) 长度为2
只要两个元素不等,就不符合要求
2)长度大于等于3
只要是诸如ABB或BAA或AAB或BBA的序列,都不符合要求
具体为什么可以自己去验证一下。只要是一串连续相同元素和两个不同元素组合在一起就一定不行,因为其中不等的两个相邻元素不存在于任何回文子串。
- 解决
最容易想到的就是对每个元素遍历其后的所有元素,统计除去不符合要求的子序列外的所有子序列个数。但是这种方法的复杂度为O(n^2),由于最后四个数据范围较大,这种方法做下来会超时。
所以可以反向思考一下,如何优化时间复杂度?
如果计算得到所有子串数量,再减去不符合要求的子串,是不是就会更快。由于不符合要求的子串规律明显、单一,因此可以设法浓缩到一次遍历就统计所有不符要求子串数。
统计思路:
由于只有两个元素,因此可以通过标记其中一个元素的位置来确定不符合要求的子串数量。
例如,ABBBBABBAABBBA
在这个子串中,将所有A的位置标记出来后容易发现,它的位置只存在相邻或不相邻两种可能。
当它们不相邻时,说明两个A之间一定存在连续的B,显然此时就存在不符合要求的子序列;当它们相邻时,说明存在连续的A序列,需要确认该连续序列的最右端。
当通过定位找到一个ABBB…BBBA这样的序列后,显然对两端的两个A,分别存在B个数个不符合要求的子序列:AB\ABB\ABB…和BA\BBA…BBA,所以不符合要求的子序列数即为B个数的两倍。
而找到AAA…AAA这的序列后,需要判定它在序列中的位置。当它包含在序列中间时,说明这个序列两端一定存在两个B,即BAAA…AAAB,因此其不符合要求的序列个数同上。若其在序列的两端,那么显然只有一端有B,同理,对于这两种情况而言不符合要求的序列数就不用乘以2了。
💡问题
1)有个小问题在于,这种方法是否会重复统计一些不符合要求的序列?答案是会的。
如上面的例子,如果拿来作为定位基准的元素A出现连续相同序列时,即AAA…AAA。在对其分析的情况中仍然算入了边界的BA和AB子串,但是在统计的第一种情况ABBB…BBBA中同样也统计了两个边界的AB和BA。也就是说,只要一个AAA…AAA的一端存在B,那么在对这个B进行统计时,就会重复算入一次边界序列。所以要把这个重复计算的序列减去。
2)如果序列的最后一个元素不是作为定位基准的元素,这就代表在最后一个基准元素到序列末尾还存在一段另一个元素,显然对于这段元素也存在不符合要求序列。因此,应该在遍历结束后,对最后一个基准元素位置进行判定,若不在最后一个,还需要减去最后一段元素序列的情况。
- 其他思路
这道题我最开始的思路是:
先统计所有的回文子串,再将所有子串进行组合得到不重复的子序列,这两部分的子串数之和即为答案。
但是在实现过程中,我想了很久也改了很久,问题依然在于如何在组合子串的时候进行去重,也就是BB、BBB、BBBB这样的重合回文子串不需要进行二次组合。
虽然感觉听上去不难,但是确实写起来很麻烦。遂弃😢
总结
- T4依然在总结教训,学着简化题目、优化思路的过程中
代码
//
// main.cpp
// test3
//
// Created by 王黎杉 on 2020/5/6.
// Copyright © 2020 王黎杉. All rights reserved.
//
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
long long n = 0,ans = 0;
long long mid[3];
int main()
{
ios::sync_with_stdio(false);
cin>>n;
string s;
cin>>s;
ans = n * (n - 1) / 2; //总子串个数
//标价所有与第一个元素相同的元素位置
long long left = 0;
//找到所有不符合要求的
//只要是abbbb或baaaa或bbbbba或aaaaab都不符合要求
for( long long i = 1 ; i < s.size() ; i++ )
{
if( s[i] == s[0] ) //下一个相同元素
{
if( i - left > 1 ) //如果两个相同元素不相邻
{
ans -= (i - left - 1) * 2; //减去中间元素个数*2(abbba)
left = i; //标记当前元素位置
}
else //如果相邻
{
while( s[i] == s[0] && i < s.size() ) //标记相邻相同元素的边界
i++;
//如果连续相同元素在序列的首部或尾部
if( i == s.size() || left == 0 )
{
if( i == s.size() && left == 0 ) //整个序列都为相同元素,直接结束
break;
ans -= (i - left - 1); //减去相同元素个数,加1个重复减掉的ab
if( i == s.size() ) //如果边界为序列末尾,直接结束
{
left = s.size();
break;
}
}
else //如果是在序列中间
{
ans -= ((i - left) * 2 - 2); //减去中间相同元素个数的2倍,加两个重复减掉的ab
}
left = i - 1; //标记最后一个元素位置
}
}
}
//如果最后一个相同元素不是末尾元素,则减去它到末尾之间的元素个数
if( left < s.size() - 1 && left > 0 )
ans -= (s.size() - left - 1);
cout<<ans<<endl;
return 0;
}