题意:一个字符串是k美的定义:字符串中不同字符出现的总次数能整除k
现在给你一个n,k,和一个字符串,n为字符串的长度,让你求一个字符串满足k美的定义并且这个字符串的字典需要尽可能的小(前提是大于等于原字符串)
并且长度和原串长度相同,如果答案字符串不存在,输出-1.
思路:
1:因为要求的字符串要尽可能的小,还必须大于等于原串,那么我们可能的最优解就是原串本身满足k美,直接输出原串。
2:如果一个串满足k美,那么说明串的长度是由一组k的倍数组成的(如k=2,n=8,n=2+4+2),所以如果n不能整除k,那么找不到满足k美的字符串,所以我们直接输出-1.
3.如果寻找满足k美的最小字符串?我们可以这样贪心的想,构造一个最小的大于等于原串的k美字符串,那么我们应该从原串后往前构造,因为越往前阶越大,构造出来
的字符串越大,所以我们尽可能在最后就构造好,这样满足最小的性质。
我们首先统计每个字符出现的次数,用一个cnt数组存储,然后统计将所有字符都变成k的倍数时所需增加的字符总数total:
即字符i]变成k的倍数时需要增加的字符数为 (k-cnt[i]%k)%k;
因为我们要从原串尽可能后的地方开始构造k美字符串,那么我们应该在原串中先找到最长的不用修改的最长前缀子串。
我们从后往前遍历,遍历到位置i时,我们判断从位置i开始(不包括位置i)之前的子串是否是可以不用修改的最长前缀子串。
然后我们得到位置i上的字符c,我们从c+1开始到z遍历判断放哪个字符。这里比较难理解,我们从c+1开始遍历,这样我们的子串就是最小的大于原串的k美串。
解释:因为我们是从后往前遍历的,定义是i前面的子串不需要修改,那我们只能改从i开始到最后的子串,可能朋友们在想为什么一定要改位置i的字符,让它变大,
理由是如果i处的字符不改,那么我们不会遍历到i,我们会遍历到i+1就结束遍历,因为此时最长子串包括i处字符,那么既然我们遍历到了i,说明i不在不用修改的最长子串里,
所以i处字符一定要修改,所以我们将i处字符增大,直到最长子串的total值-1,然后我们i后面的所有字符是可以任意构造的(因为i处字符比原串大,所以就算i后面都是a也比原串大)
那么我们遍历cnt数组,把字典序越大的字符放在最后,然后如果长度不够就补a
以 n=4,k=2 bacd 为例
假设遍历到d,判断方法:我们把之前统计的total拿来,明显total的值为4,代表需要添加四个字符才能将原来子串中四个字符变成k的倍数。
那么我们遍历到d的时候,可能的最长子串是bac,求子串bac的total值 我们只需要这样:
total-=(k-cnt[temp[i]-'a']%k)%k;
cnt[temp[i]-'a']--;
total+=(k-cnt[temp[i]-'a']%k)%k;
这样total值明显为3,而i的值为3,代表只有1位构造位给你(n=i),total的值只能减1,
则我们继续向前遍历,直到满足total的值能减到1后i+1+total<=n,
这样说明i后面的构造位长度大于total,可以构造
代码:
#include <iostream>
#include <string>
#include <string.h>
using namespace std;
int n,t,k;
string temp;
int alpha_bet[30];
int cnt[27];
void solve()
{
memset(cnt,0,sizeof(cnt));
for(int i=0;i<n;i++)
{
cnt[temp[i]-'a']++;
}
int total=0;
for(int i=0;i<26;i++)
{
total+=(k-cnt[i]%k)%k;
}
if(total==0) cout<<temp<<endl;
else if(temp.size()%k!=0) cout<<"-1"<<endl;
else
{
bool is_continue=true;
for(int i=n-1;i>=0&&is_continue;i--)
{
total-=(k-cnt[temp[i]-'a']%k)%k;
cnt[temp[i]-'a']--;
total+=(k-cnt[temp[i]-'a']%k)%k;
//统计i位置前的tota数量
for(int j=temp[i]-'a'+1;j<=25;j++)
{
int last=total;
total-=(k-cnt[j]%k)%k;
cnt[j]++;
total+=(k-cnt[j]%k)%k;
//看i位置前的total数量能减否减到0
if(i+1+total<=n)
{
for(int u=0;u<i;u++) cout<<temp[u];
cout<<char(j+'a');//输出i位置的字符
for(int u=1;u<=n-(i+1)-total;u++) cout<<'a';//补a
for(int u=0;u<=25;u++)
//因为输出是从前往后,所以从u=0开始
{
int circle=(k-cnt[u]%k)%k;
while(circle>0)
{
total--;
circle--;
cout<<char(u+'a');
}
}
cout<<endl;
is_continue=false;
break;
}
cnt[j]--;
total=last;
}
}
}
}
int main()
{
cin>>t;
while(t--)
{
cin>>n>>k;
cin>>temp;
solve();
}
}