题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6586
题意:给你一个字符串,让你找出一个字典序最小的长度为k的字符串,并且需要满足以下26个限制(即a-z的数量限制)
每个字符出现的最少次数和最多次数
思路:先用26队列把每个字符出现的位置一一存起来,要求的字符串是字典序最小的 所以需要从a开始枚举到z,
字符串的长度是k,对于每一个位置来说,a-z都有可能,所以枚举每个位置,对于每个位置枚举26个字母,如果可以放进答案数组就放,放不下就break,判断的时候,用上给你的26个限制条件就可以了
对于一个位置,假设一个字符满足条件,然后判断一下
1、这26个字符 用了的字符数+这个位置之后还没有用的字符数够不够题目要求的最低限制。
2、这26个字符 假设只用最低限制数量的字符 判断是否比k还大,假设用上最多限制的字符,判断是否比k还小
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <vector>
#define ll long long
const int maxn = 1e6+5;
using namespace std;
char s[maxn];
int k, len, cl,flag;
int l[30],r[30];
int cnt[100010][30];
int num[30];
int ans[maxn];
int main()
{
while(scanf("%s%d",s+1,&k)!=EOF) {
for(int i=0;i<26;i++) {
scanf("%d%d",&l[i],&r[i]);
num[i]=0;
}
queue<int> q[30];//如果不定义在while循环内部,记得清空队列
len = strlen(s+1);
for(int i=1;i<=len;i++)q[s[i]-'a'].push(i);
for(int i=0;i<26;i++)cnt[len+1][i]=0;//初始化cnt数组
for(int i=len;i>0;i--) {//cnt[i][j]记录的是i位置之后j字符的数量
for(int j=0;j<26;j++) {
if(s[i]=='a'+j) cnt[i][j]=cnt[i+1][j]+1;
else cnt[i][j]=cnt[i+1][j];
}
}
cl=0;//已经放好的字母的位置
for(int pos=1;pos<=k;pos++) {
flag=0;
for(int i=0;i<26;i++) {
if(num[i]>=r[i]) continue;
while(!q[i].empty() && q[i].front()<=cl) q[i].pop();
if(!q[i].empty()) {
int ok=1;
num[i]++;
for(int j=0;j<26;j++) {//判断用上当前位置字符i之后,剩下的字符是否满足最低限制
if(cnt[q[i].front()+1][j]+num[j]<l[j]){
ok=0;
break;
}
}
if(ok) {//满足最低限制之后,判断 如果只用上最低数量的字符是否比k大,
//如果都用上最大数量的字符是否比k小
int lit1=0,lit2=0;
for(int j=0;j<26;j++) lit1 += max(l[j]-num[j],0);
for(int j=0;j<26;j++) lit2 += min(r[j]-num[j],cnt[q[i].front()+1][j]);
if(lit1>k-pos || lit2 <k-pos) ok=0;
}
if(!ok) {//如果不满足,那么这个位置的字符i不能当做答案
num[i]--;
}
else {//如果能,则更新cl,记录答案,pos位置满足,flag=1;
cl=q[i].front();
ans[pos]=i;
flag=1;
break;
}
}
}
if(!flag) break;//如果a-z都不能放入答案那么结束循环,输出-1;
}
if(flag) {
for(int i=1;i<=k;i++)
printf("%c",ans[i]+'a');
printf("\n");
}
else printf("-1\n");
}
return 0;
}