20171029D-吃货的烦恼

题目大意

从一段字母里任选一段 使得这一段中(出现最多的字符的个数)-(出现最少的字符的个数)最大(这两种字符最少出现一次)
字母串长度<=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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值