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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章