Codeforces Round #705 (Div. 2) 1493C. K-beautiful Strings题解

C. K-beautiful Strings

c题传送门
在这里插入图片描述
在这里插入图片描述

题意:

题意比较简单,给我们一个由小写字母组成的字符串S,长度为n(n<=1e5),然后给我们一个k(k<=n),要我们找出比字典序尽可能小但不小于 S 的字符串,这个字符串还需满足每种字母出现的个数都是k的倍数。

题解:

贪心+构造+前缀和+枚举

1.首先判断n%k=0是否成立,若不成立,则一定构不成满足题意的字符串。因为若n%k>0,那么就说明存在某种字母的个数就不是k的倍数。

2.在n%k==0的前提下,在判断原字符串S是不是就是答案,只需判断一下字符串S中每种字母出现的个数是不是都满足是k的倍数即可。

3.若S不是答案,那么我们就要再考虑三点。
①枚举。怎样才能比S大,此处请思考半分钟,其实只需S中的字符串任何一位字母变大就行,例如abcd,我把b换成c得accd就比abcd大了,同样b换成d、e、f…同样可以,其他位置的字母同样可以这样操作。
②贪心。既然任何位置上的字母只需换成更大就行,那么这个时候我们再考虑如何尽可能最小化,此处请思考半分钟,既然要最小化,那么我们肯定应该从后往前枚举,优先让后面的字母变大。然后回到前面的例子,既然我把b换成了c,那么后缀其实都可以换成a,即acaa也比abcd大,同样其他位置上的字母也一样,并且我们需要优先将后面的字母先变大。
③前缀和。现在我们得到了比S字典序大的同时尽可能小的字符串了,这时还要满足一个条件,那就是每种字符串出现的个数是k的倍数。首先对与0<i<n-1的每个位置的26个字母出现的个数的前缀和预处理出来。我们在枚举变大某一位置(位置p)字母时check一下,若某个字母(字母x)个数要满足是k的倍数,那么在该位置之后需要再补上(k-sum[p][x]%k)%k个字母x(这里可以自己推一推为什么是这个式子),同样对于其他的25个字母也这样操作一下,看看剩余的n-p个位置够不够,只要够,那么再根据②的原则就可以构造出所需字符串了。

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
int n,k,sum[MAXN][30];
int cnt[30];
string s;
int check(int i){
    for(int j=s[i]-'a'+1;j<26;j++){//每个位置还可以枚举比原位置大的字母
        memset(cnt,0,sizeof(cnt));
        int have=n-i-1;//再i位置之后还有n-i-1个位置可以放字母
        for(int x=0;x<26;x++){
            int temp=sum[i][x];
            if(x==s[i]-'a') temp--;
            if(x==j) temp++;
            have-=(k-temp%k)%k;
            cnt[x]+=(k-temp%k)%k;//某类字母还需多少个
            if(have<0)//若不够,那肯定不成立
                break;
        }
        if(have<0) continue;
        cnt[0]+=have;
        return j;
    }
    return 0;
}
string getans(int i,int j){
    string ans=s.substr(0,i)+char(j+'a');
    for(int x=0;x<26;x++){
        ans+=string(cnt[x],x+'a');
    }
    return ans;
}
void solve(){
    if(n%k>0){
        cout<<-1<<'\n';
        return ;
    }
    int ok=true;
    for(int i=0;i<n;i++){
        for(int j=0;j<26;j++){
            if(i==0){
                sum[i][j]=0;
                if(j==s[i]-'a')
                    sum[i][j]++;
                continue;
            }
            sum[i][j]=sum[i-1][j];
            if(j==s[i]-'a'){
                sum[i][j]++;
            }
        }
    }
    for(int i=0;i<26;i++){
        if(sum[n-1][i]%k!=0){
            ok=false;
            break;
        }
    }
    if(ok){
        cout<<s<<'\n';
        return ;
    }
    for(int i=n-1;i>=0;i--){//逆序枚举每一个位置
        int flag=check(i);
        if(flag){
            cout<<getans(i,flag)<<'\n';
            return ;
        }
    }
    return ;
}
int main(){
    ios::sync_with_stdio(false);
    int t;cin>>t;
    while(t--){
        cin>>n>>k>>s;
        solve();
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值