牛客练习赛85 B 音乐家的曲调 DP 尺取

传送门

题意:

给出一个全由小写字母组成的字符串,让你找出三个区间,这三个区间不能重合,并且每个区间内1,每个字母出现的顺序不能超过m次,找出使得这三个区间长度之和最大的情况

题解:

1,如何找出最长的一个区间使得每个字母出现的次数不超过m次

用一个数组记录26个字母分别出现多少次,再用一个指针记录在当前位置下,在保证每个字母出现次数都不超过m的条件下,区间往左最长能有多长

当遍历到第i位,某个字母出现了m+1次时,记此时上述指针指向的位置是k,则令endp[k]=i-1,然后k++

i遍历完成整个字符串后,令当前k右边所有字符的right_broad都为字符串最末一个字符的位置

endp[i]-i+1中的最大值就是最长的区间

2,如何找出三个区间使得总的区间和最大

从后向前dp

第一次dp寻找在仅保留第i位及其右边的字符的情况下,最长的符合条件的区间

dp1[i]=max(dp1[i+1],endp[i]-i+1);

第二次dp寻找在仅保留第i位及其右边的字符的情况下,两个符合条件的区间的长度和的最大值

dp2[i]=max(dp2[i+1],endp[i]-i+1+dp1[endp[i]+1]);

这个状态转移公式解释一下,从第i位起的那个符合条件的区间要全取走,然后就剩下了从第endp[i]+1位起右边的剩余字符了,这时候dp1里面存储的就是答案

第三次dp同理

dp3[i]=max(dp3[i+1],endp[i]-i+1+dp2[endp[i]+1]);

#include<iostream>
#include<string>
#include<queue>
/*struct Point{
    int id;
    char c;
}
Point point(int a,char b){
    Point tmp;
    tmp.id=a;
    tmp.c=b;
    return tmp;
};*/
using namespace std;
int alphabet[200];
int endp[10000007];
int dp1[10000007],dp2[10000007],dp3[10000007];
string s;
int main(){
    int n,m;
    cin>>n>>m;
    cin>>s;
    int l=n;
    queue<int> que[200];
    for(int i=0;i<l;i++){
        if(alphabet[s[i]]==m){
            endp[que[s[i]].front()]=i-1;
            alphabet[s[i]]--;
            que[s[i]].pop();
        }
        alphabet[s[i]]++;
        que[s[i]].push(i);
    }
    for(int i='a';i<='z';i++){
        while(!que[i].empty()){
            endp[que[i].front()]=l-1;
            que[i].pop();
        }
    }
    for(int i=l-2;i>=0;i--){
        endp[i]=min(endp[i],endp[i+1]);
    }
    /*for(int i=0;i<l;i++){
        cout<<endp[i]<<" ";
    }
    cout<<endl;*/
    //int dp1=l-1,dp2=l-1,dp3=l-1;
    for(int i=l-1;i>=0;i--){
        dp1[i]=max(dp1[i+1],endp[i]-i+1);
    }
    for(int i=l-1;i>=0;i--){
        dp2[i]=max(dp2[i+1],endp[i]-i+1+dp1[endp[i]+1]);
    }
    for(int i=l-1;i>=0;i--){
        dp3[i]=max(dp3[i+1],endp[i]-i+1+dp2[endp[i]+1]);
    }
    cout<<dp3[0]<<endl;
    return 0;
}

 

posted @ 2021-06-25 22:51  Isakovsky  阅读(55)  评论(0编辑  收藏  举报