题目大意
从一段字母里任选一段 使得这一段中(出现最多的字符的个数)-(出现最少的字符的个数)最大(这两种字符最少出现一次)
字母串长度<=100 0000
其实我最疑惑的是 他的美味值为什么能这样定义…
题解
简化题意
做题目肯定是从小的地方下手
n<=100 0000我们改不了 那就从n的系数下手
logn的操作做不来 所以只能找到另外的下手点–字母的个数
因为字母的个数只有26个
26*n是不会超时的
前面再乘个小数字应该也能卡过去
所以我们就枚举字母吧
由于我们真的不知道某一段里什么字母最多
所以我们假设 字母A最多 字母B最少
然后根据题意 我们就可以把这一串字母简化成一堆数字
简化方法:所有的A变成+1 所有的B变成-1 其他的字母变成0
例
acbccb
我们假设当前最多的是a 最少的是c
那么简化之后
+1 -1 0 -1 -1 0
然后题目就变成了 求一段长度的子串 使得这串子串和最大
就变成了一个前缀和题目了…
关于前缀和题目
对于一串数字 求其中的某连续一段使得其和最大
解法
举个例子
记前缀和为sum[i]
对于数字串 1 1 -3 1 2 -1
对于sum[3]=-1
如果把前三个数字加进去
无论后面的数字怎么加
最后的结果都要-1 (都要加上sum[3])
所以我们干脆就不加上sum[3]
从第四个数字开始加
比如sum[5]=2 但是如果从第四个数字开始加就 =3
怎么都比从头开始加好
所以说 我们就记 f[i]为从当前起点 加到i的数字
(例子中f[5]的起点就是4)
假如f[i]<0 就表明
后面的子串和
以f[i]的起点为起点 怎么都会比以 i+1为起点 小
所以 遇到f[i]<0 之后的子串和 就以i+1为起点重新累加
但是感觉讨论A和B还是会超时(26*26=…反正是个三位数)
所以我们就要想办法减少讨论的次数
容易注意到 对于第i个字母 与它相关的值最多只有52个
(即以它为最多个数和最少个数的情况)
因此 我们重新回到以每一个位子上的字母为单位讨论
对于每一个字母 更新以它为A的值 和 以它为B的值 即可
注意
这道题目细节有很多
其中最重要的一个点 A和B至少都要在子串中出现一次
因此我开了专门记录状态的数组
还有就是 子串可能以 B为开头(例如abbbbbbbb)
这种情况需要单独讨论(你WA了就知道是为什么了)
当然也可以像我一样 把所有的情况分为以A开头和以B开头讨论
以上两个细节我通过自己造数据卡了半个小时才卡出来
简直...
对拍代码
#include <iostream>
#include <cstdio>
using namespace std;
//maxf记录的就是最大值
int p[27][27],ap[27][27],maxf[27][27];//P为子串以A开头的情况
int q[27][27],aq[27][27];//Q为子串以B开头的情况
//ap aq为状态记录 ap[A][B]的二进制分别记录A B是否出现
int main()
{
freopen("chi.in","r",stdin);
freopen("chi.out","w",stdout);
int n,c,res=0;
scanf("%d\n",&n);
for(int k=1;k<=n;k++)
{
c=getchar()-'a'+1;
for(int i=1;i<=26;i++)
{
if(i==c)continue;
if(ap[i][c]&2)
{
p[i][c]--;
if(p[i][c]<0)ap[i][c]=p[i][c]=0;
else ap[i][c]=3;
maxf[i][c]=max(maxf[i][c],p[i][c]);
}
ap[c][i]|=2;
p[c][i]++;
if(ap[c][i]==3)maxf[c][i]=max(maxf[c][i],p[c][i]);
if((!aq[i][c])||q[i][c]-1<-1)aq[i][c]=1,q[i][c]=-1;
else q[i][c]--;
if(aq[c][i])aq[c][i]=3,q[c][i]++;
if(aq[c][i]==3)maxf[c][i]=max(maxf[c][i],q[c][i]);
}
}
for(int s=1;s<=26;s++)
for(int e=1;e<=26;e++)
res=max(res,maxf[s][e]);
printf("%d",res);
}