CodeForces-Round-705C-K-beautiful Strings题解

题意:一个字符串是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();

	}
}

 

 

 

 

 

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值