http://icpc.upc.edu.cn/problem.php?cid=1822&pid=8
题目描述
Tom has a string containing only lowercase letters. He wants to choose a subsequence of the string whose length is k and lexicographical order is the smallest. It's simple and he solved it with ease.
But Jerry, who likes to play with Tom, tells him that if he is able to find a lexicographically smallest subsequence satisfying following 26 constraints, he will not cause Tom trouble any more.
The constraints are: the number of occurrences of the ith letter from a to z (indexed from 1 to 26) must in [Li,Ri].
Tom gets dizzy, so he asks you for help.
输入
The input contains multiple test cases. Process until the end of file.
Each test case starts with a single line containing a string S(|S|≤105)and an integer k(1≤k≤|S|).
Then 26 lines follow, each line two numbers Li,Ri(0≤Li≤Ri≤|S|).
It's guaranteed that S consists of only lowercase letters, and ∑|S|≤3×105.
输出
Output the answer string.
If it doesn't exist, output −1.
样例输入
复制样例数据
aaabbb 3
0 3
2 3
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
0 0
样例输出
abb
题意:求该串的一个长度为k的子序列,要求该子序列中任意字母的数目满足条件限制且字典序最小
分析:如果只是求字典序最小的长度为k的子序列可以用序列自动机(就是用队列保存字母的下标,然后贪心暴力,时间复杂度O(26*N)),但是这道题目对每一种字母的出现次数做了限制,那在贪心的时候把限制加上就好了
求长度为k的字典序子序列题目https://nanti.jisuanke.com/t/39614(好像也可以用单调栈)
//#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long ll;
const int N=1e5+5;
char s[N];
int num[30];//已经用了多少个某字母
int last[N][26];//i位置后(包括i),还有多少个j字母
char ans[N];//存结果
int l[30],r[30];//最小数目限制,最大数目限制
int main(){
int k;
while(~scanf("%s%d",s+1,&k)){
queue<int> q[30];
int len=strlen(s+1);
memset(num,0,sizeof(num));
for(int i=1;i<=len;i++){
q[s[i]-'a'].push(i);
}
for(int i=0;i<26;i++){
last[len+1][i]=0;
}
for(int i=len;i>=1;i--){
for(int j=0;j<26;j++){
if(s[i]=='a'+j) last[i][j]=last[i+1][j]+1;
else last[i][j]=last[i+1][j];
}
}
for(int i=0;i<26;i++){
scanf("%d%d",&l[i],&r[i]);
}
int lp=0;//上一个确定好的字母在原串中的下标
bool flag;
for(int pos=1;pos<=k;pos++){//枚举每一位
flag=false;
for(int i=0;i<26;i++){
if(num[i]==r[i]) continue;//已经达到最大限制,该字母不能用了
while(!q[i].empty()&&q[i].front()<=lp) q[i].pop();//下标小于等于lp的肯定不能用了,出队
if(!q[i].empty()){
bool ok=true;//假设这个字母可以放在这个位置
num[i]++;
for(int j=0;j<26;j++){
if(last[q[i].front()+1][j]+num[j]<l[j]) {//剩下的该字母+用了的j字母已经不能达到最小限制
ok=false;
break;
}
}
if(ok){
int lim1=0;//后面字母假设全拿最小限制,还能拿多少字母
int lim2=0;//后面字母假设全拿最大限制 ,还能拿多少字母
for(int j=0;j<26;j++) lim1+=max(l[j]-num[j],0);
for(int j=0;j<26;j++) lim2+=min(r[j]-num[j],last[q[i].front()+1][j]);
if((lim1>k-pos)||(lim2<k-pos)) ok=false;
}
if(!ok) {
num[i]--;
}
else{
ans[pos]='a'+i;
flag=true;
lp=q[i].front();
break;
}
}
}
if(!flag) break;
}
if(!flag) printf("-1\n");
else{
for(int i=1;i<=k;i++){
printf("%c",ans[i]);
}
printf("\n");
}
}
return 0;
}