字符串魔法hard(前后缀与贪心)
链接:https://ac.nowcoder.com/acm/contest/9680/C
来源:牛客网
description:
白浅获得了一个仅由A和B组成的字符串。他可以至多使用一次魔法来改变字符串。 魔法:选择一个子串,满足子串中 A 的数量等于 B
的数量,然后按字典序从小到大排序这个子串,即变成形如AAA…AAABBB…BBB这样的字符串(A和B的数量均与原来的子串相同)。
他想知道,在他至多使用一次魔法后,这个字符串能够出现的最长的字典序不递减的子串的长度为多少。
输入描述: 第一行包含一个整数n,代表字符串的长度。 接下来一行给出一个长度为n的字符串。
1≤n≤200000
输出描述: 输出一行一个正整数表示答案。
Sample:
输入 :
6
AABBAA
输出:
6
解题思路:
- 首先,我们可以很容易想到拿一个前缀数组去保存AB的数量差,我们这里设A为正,B为负,得到一个prefix_sum数组,prefix_sum[i]=0即代表s到si的子串中A、B数量相等。
- 对于前缀数组prefix_sum,它可以令我们知道哪些子串是AB数量相等的,具体怎么实现,就是从暴力开始,最后发现其有一定的贪心性质,然后利用一个map实现优化,当然直接一个数组也行,但其下标有正有负所以需要做一些处理,我这里就直接map了。具体看代码注释。
- 知道了哪些子串AB数量相等后,我们还要知道连续A子串和连续B子串的数据,根据题意得知最后要求的是最长的字典序不递减的子串的长度,所以可以设置一个preA数组和sufB数组分别表示以s[i]为末尾的连续A子串长度和以s[i]为开头的连续B子串长度。
源码与注释:
#include<iostream>
#include<map>
#include<algorithm>
using namespace std;
const int N = 2e5 + 10;
map<int, int> M; //M[k]存放字符串s中前缀和为k的最后下标i, 即最大的i使s[1]-- - s[i]的代数和为k
char s[N];
int n;//字符串长度<=200000
int prefix_sum[N];//以A为正,B为负,存放前缀和,和为0即代表A、B数量相等
int preA[N];//preA[i]表示以s[i]为末尾的连续A子串长度
int sufB[N];//sufB[i]表示以s[i]为开头的连续B子串长度
//preA和sufB的前后缀性质是由题目中最终要求的 字典序不递减 决定的。
int main()
{
cin >> n >> s + 1;
//求前缀和prefix_sum
for (int i = 1; i <= n; i++) {
if (s[i] == 'A' )
prefix_sum[i] = prefix_sum[i - 1] + 1;
else
prefix_sum[i] = prefix_sum[i - 1] - 1;
}
//求preA和sufB以获得连续子串的数据
for (int i = 1, j = n; i <= n; i++, j--) {
if (s[i] == 'A')
preA[i] = preA[i - 1] + 1;
if (s[j] == 'B')
sufB[j] = sufB[j + 1] + 1;
}
//求出sufB和preA后,按照暴力的思想可以像下面这样遍历,但无奈n的规模有200000,显然是不行的
//int MAX = -1;
//for (int i = 0; i <= n; i++) {
// for (int j = i + 2; j <= n; j++) {
// if (prefix_sum[j] - prefix_sum[i] == 0) {
// MAX = max(MAX, j - i + preA[i] + sufB[j + 1]);
// }
// }
//}
//cout << MAX << endl;
//以下用一个map M存放前缀和的一些信息以做优化
for (int i = n; i >= 1; i--) {
if (M[prefix_sum[i]] == 0) {
M[prefix_sum[i]] = i;//其中M[k]存放字符串s中前缀和为k的最后下标i,即最大的i使s[1]---s[i]的代数和为k
}
}
//这里有点贪心的思维,即在求M时只看最后的下标
//这样一来,我们知道s[1]---s[i]前缀和为prefix_sum[i],则最长的前缀和为k的子串为s[1]---s[ M[prefix_sum[i]] ],
//所以有了M,我们就知道以s[i+1]为头的最长的AB数相等的子串为s[i+1]---s[ M[prefix_sum[i]] ],其长度为M[prefix_sum[i]]-i
//所以我们也不用去做过多的循环了,一轮贪心选择即可完成
int MAX = -1;
for (int i = 1; i <= n; i++) {
//以s[i+1]为头的最长子串及连接两边AB后的长度。
int nowLength = (M[prefix_sum[i]] - i) + (preA[i] + sufB[M[prefix_sum[i]] + 1]);
MAX = max(MAX, nowLength);
}
cout << MAX << endl;
return 0;
}
纯源码:
#include<iostream>
#include<map>
#include<algorithm>
using namespace std;
const int N = 2e5 + 10;
map<int, int> M;
char s[N];
int n,prefix_sum[N],preA[N],sufB[N];
int main()
{
cin >> n >> s + 1;
for (int i = 1; i <= n; i++) {
if (s[i] == 'A' )
prefix_sum[i] = prefix_sum[i - 1] + 1;
else
prefix_sum[i] = prefix_sum[i - 1] - 1;
}
for (int i = 1, j = n; i <= n; i++, j--) {
if (s[i] == 'A')
preA[i] = preA[i - 1] + 1;
if (s[j] == 'B')
sufB[j] = sufB[j + 1] + 1;
}
for (int i = n; i >= 1; i--) {
if (M[prefix_sum[i]] == 0) {
M[prefix_sum[i]] = i;
}
}
int MAX = -1;
for (int i = 1; i <= n; i++) {
int nowLength = (M[prefix_sum[i]] - i) + (preA[i] + sufB[M[prefix_sum[i]] + 1]);
MAX = max(MAX, nowLength);
}
cout << MAX << endl;
return 0;
}
结果:
最后贴一张AC图
就酱:)