链接:http://acm.hdu.edu.cn/showproblem.php?pid=6586
题意:给你一个字符串和k,接下来26行给出每个字母的个数限制(L[i],R[i])。让你构造一个字典序最小的长度为k的满足限制条件的一个子序列串(可以不连续,但要符合顺序),没有则输出-1。
思路:将26个字母各自放入队列中,并记下每个位置各字母的后缀和。往k个位置贪心的放字母,先从a开始尝试。首先字母的数量不能大于最大限制,然后位置要大于上一个放的字母。要放这个字母还满足两个条件。
1.对于26个字母,其后的个数加上当前选的个数要小于L[i]。
2.对于26个字母,把其后的个数取最小之后和不能大于k,把其后的个数取最大之后和不能小于k。
就这样贪心的放,再检查是否符合条件。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5+10;
char s[N],ans[N];
int l[30],r[30],hou[N][30],num[30],k,len;
void Init()
{
for(int i=1;i<=26;i++)
hou[len+1][i]=0,num[i]=0;
return ;
}
int main(void)
{
while(~scanf("%s%d",s+1,&k))
{
bool flag=1;
queue<int> q[30];
len=strlen(s+1);
Init();
for(int i=1;i<=26;i++)
scanf("%d%d",&l[i],&r[i]);
for(int i=len;i>=1;i--) //每个字母的后缀和
for(int j=1;j<=26;j++)
hou[i][j]=hou[i+1][j]+(s[i]=='a'+j-1);
for(int i=1;i<=len;i++)
q[s[i]-'a'+1].push(i);
int now=0,cur;
for(int i=1;i<=k;i++)
{
bool flag1=0;
for(int j=1;j<=26;j++)
{
//数量已达到最大就不能放该字母
if(num[j]==r[j]) continue;
//位置要大于上一个所放字母的位置
while(!q[j].empty()&&q[j].front()<=now) q[j].pop();
if(!q[j].empty())
{
bool no=0;
cur=q[j].front();
//先把他放上
num[j]++;
//之后的个数是否小于最小限制
for(int ii=1;ii<=26;ii++)
if(hou[cur+1][ii]+num[ii]<l[ii])
{
no=1;break;
}
//不能放则改变数量,之后的数量肯定更小,直接continue。
if(no)
{
num[j]--;
continue;
}
int minn=0,maxx=0;
for(int ii=1;ii<=26;ii++)
minn+=max(l[ii]-num[ii],0),maxx+=min(r[ii]-num[ii],hou[cur+1][ii]);
if(minn+i>k||maxx+i<k)
no=1,num[j]--;
if(!no)
{
now=q[j].front();
ans[i]='a'+j-1;
//cout<<'a'+j-1<<endl;
if(i==k) ans[i+1]='\0';
flag1=1;
break;
}
}
}
if(!flag1)
{
flag=0; break;
}
}
if(flag)
printf("%s\n",ans+1);
else
printf("-1\n");
}
}