题目大意: 给出你长度为n的字符串s,构造出一个大于等于s,长度为n的字符串ans,使得字符串ans中的出现的字符次数能整除m。
思路: 建立(构造) 一个后缀连续字符数组(为了缩短后面的判断时间)
dp[i][j] 表示第i位上后面(dp[i][j]-1)个‘a’+j字符,就是连续。
用flag表示是否已经满足“大于字符串这一条件”
- 不满足,枚举当前位能放的字符(大于等于当前字符),之后判断是否还有该字符可用(我们每次开一个字符都是k个的开,这样可以直接满足字符数量被k整除) (1)没有可用的,开一个新k组该字符还有(k-1)个可用,总可用数量减k。然后判断是否大于原串。(2) 有可用的就直接用。
- 满足之后,剩下的全部都变成’a’。因为每次取得是k。所以剩的也是k得倍数。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e5+10;
char a[maxn];
int n,k,b[30],c[30],las[maxn][27],top;
char ans[maxn];
bool isok(int nxt,int c[]) {
while( nxt<=n ) {
for(int i='z'; i>='a'; i--) {
if( c[i-'a']==0 ) continue;///已满或者没有
if( i>a[nxt] ) return true;///已经比较大了
else if( i<a[nxt] ) return false;///当前位置比原小
else if( c[i-'a']>=las[nxt][i-'a'] ) {
///够后面用的 连续
c[i-'a'] -= las[nxt][i-'a'], nxt += las[nxt][i-'a'];
break;
} else if( c[i-'a']<las[nxt][i-'a'] ) return false;///不够
}
}
return true;//一直相等,也是合法的
}
int main() {
int t;
cin >> t;
while( t-- ) {
cin >> n >> k >> ( a+1 );
if( n%k!=0 ) {
cout << -1 << endl;
continue;
}
for(int i=n; i>=1; i--)
for(int j='a'; j<='z'; j++) {
if( a[i]==j ) las[i][j-'a'] = las[i+1][j-'a']+1;///后续连续字符
else las[i][j-'a'] = 0;
}
int flag = 1,sum = n;///flag来判断是否已经满足大于要求
for(int i=1; i<=n; i++) {
if( flag ) {
///未满足
for(char j=a[i]; j<='z'; j++) {
///枚举大于等于当前字符
if( b[j-'a'] ) {
///可用数量
memcpy( c,b,sizeof c );
c[j-'a']--;///放 j这个字符
c['z'-'a'] += sum;///剩下的都变成z字符
if( j>a[i] || isok(i+1,c) ) {
ans[i] = j;
b[j-'a']--;
if( j>a[i] ) flag = 0;
break;
}
} else {
if( sum<k ) continue;///剩下的不够新增字符的,
memcpy( c,b,sizeof c );
c[j-'a'] += k-1;
c['z'-'a'] += sum-k;///用最大的情况判断是否可行。
if( j>a[i] || isok(i+1,c) ) {
ans[i] = j;
sum -= k;
b[j-'a'] = k-1;
if( j>a[i] ) flag = 0;
break;
}
}
}
} else {
///字典序已经比较大了,后面怎样都无所谓,那么从'a'开始一直往后面放
b[0] += sum;
int id = i;
for(char j='a'; j<='z'; j++) while( b[j-'a'] ) ans[id++]=j, b[j-'a']--;
break;
}
}
for(int i=1; i<=n; i++) cout << ans[i];
cout << endl;
for(int i=1; i<=n; i++) memset( las[i],0,sizeof las[i] );
memset( b,0,sizeof b );
}
}