10.26 T1 最大子串权值

*PS:一上来第一题就是一个思维题qwq,emmm…考场上只打了个暴力,果断60分滚粗。%%那些在考场上打出正解的dalao。

                         题目来源:钟长者(侵删)

               【问题描述】

                                         你是能看到第一题的 friends 呢。
                                                                       ——hja

  何大爷对字符串十分有研究,于是天天出字符串题虐杀 zhx。 何大爷今天为字符串定义了新的权值计算方法。一个字符串由小写字母组成,字符串的权值被定义为其中出现次数最多的字符的次数减去出现次数最少的字符的次数。(注意,在讨论出现最少的字符的时候,该字符必须至少出现一次)现在何大爷给你一个字符串,何大爷想知道这个字符串的所有子串中权值最大的权值是多少?

              【输入格式】

                      第一行一个整数n,代表字符串的长度。
                      接下来一行n个小写字母,代表该字符串。

              【输出格式】

                        一行一个整数代表答案。

              【样例输入】

                             10
                           aabbaaabab

              【样例输出】

                             3

             【数据范围与规定】

                       对于30%的数据, 1 ≤ n ≤ 100。
                       对于60%的数据, 1 ≤ n ≤ 1000。
                       对于100%的数据, 1 ≤ n ≤ 106.


60 分暴力

n^2 枚举区间,更新答案。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;

int N,maxn,minn,ans=0;
int app[30]; 
char a[1000010];

int main()
{
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
    scanf("%d",&N);
    scanf("%s",a);
    for(int i=0;i<N;++i)
    {
        memset(app,0,sizeof(app));
        maxn=minn=a[i]-'a';
        ++app[a[i]-'a'];
        for(int j=i+1;j<N;++j)
        {
            int k=a[j]-'a';
            ++app[k];
            if(maxn==k&&minn!=k) maxn=k;
            else 
            {
                swap(maxn,minn);
                for(int e=0;e<26;++e)
                if(app[e])
                {
                    if(app[e]>app[maxn]) maxn=e;
                    if(app[e]<app[minn]) minn=e;
                }
            }   
            ans=max(ans,app[maxn]-app[minn]);
        }
    }
    printf("%d",ans);
    return 0;
}

满分做法

发现10^6 nlogn很吃力,考虑常数比较大的 O(n)的做法。
我们发现 n^2的做法中有很多不必要的转移,枚举了很多对答案没有贡献的区间。所以考虑能不能把多余的枚举去掉?
emmm…既然答案肯定是一个字母的出现次数 - 另一个字母的出现次数。
so??
定义 sum[k] 为当前扫到第i个数时1~i中字母k出现的次数。若k为最优子串的出现次数最多的那个字符,那我们需要枚举a~z (!=k)并把它当做出现次数最少的那个字符来更新答案,设枚举的字符为j。可知被更新的最优区间的答案为 sum[k]-sum[j]-(sum’[k]-sum’[j]) ,其中的sum’[k]-sum’[j]表示 1~i 中 的字符k的次数 - 字符j的次数最少的那个区间(1~x)的答案(可能为负数),就相当于从1~i 中舍弃了1~x 这个区间来得到最优区间的答案。反之同理。
sum’[k]-sum’[j]可以在之前就记录下来,另开一个数组f[k][j]表示。
但是我们枚举到的最优区间可能并不包含j,这样算出的答案就是错的,所以我们还需记录一个last[j]表示j最后出现的位置,pos[k][j]记录f[k][j]的右端点,如果last[j]==pos[k][j],则需要把最优区间的答案-1(把前面的j算上)。

特判:sum为0的情况

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;

int N,ans;
int sum[30],last[30];
int f[30][30],pos[30][30];
char s[1000010];

int main() 
{
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
    scanf("%d",&N);
    scanf("%s",s+1);
    for(int i=1;i<=N;++i)
    {
        int k=s[i]-'a';
        ++sum[k];
        last[k]=i;
        for(int j=0;j<26;++j)
           if(sum[j]&&j!=k)
           {
               ans=max(ans,sum[k]-sum[j]-f[k][j]-(last[j]==pos[k][j]));
               ans=max(ans,sum[j]-sum[k]-f[j][k]-(last[j]==pos[j][k]));
           }
        for(int j=0;j<26;++j)
        {
            if(f[k][j]>sum[k]-sum[j])
               f[k][j]=sum[k]-sum[j],pos[k][j]=i;
            if(f[j][k]>sum[j]-sum[k])
               f[j][k]=sum[j]-sum[k],pos[j][k]=i;
        } 
    }
    printf("%d",ans);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章