C. K-beautiful Strings
题意:
题意比较简单,给我们一个由小写字母组成的字符串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;
}