题目地址:http://acm.hdu.edu.cn/showproblem.php?pid=6586
题目:
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 262144/262144 K (Java/Others)
Problem Description
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.
Input
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
Output the answer string.
If it doesn't exist, output −1.
Sample Input
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
Sample Output
abb
解题思路:
先得到序列自动机的nxt[][]数组,同时再开一个数组记录nsum[i][j]记录i及i之后j+‘a'字母出现的次数
因为要求字典序最小的子序列,在构造这个子序列时:每次都按照a->z的顺序判断最先可以放哪个字母,可以放就把这个字母记录入答案,继续寻找下一个位置的可以放的字母,如果当前位置a->z的所以字母都不可以放,那么子序列无解,输出-1
判断是否可以放入的条件:(一定要考虑全面!)
假定这个字母可以放,以此为前提判断是否满足下面三个条件之一:
leastNum:放完这个字母后至少还要放leastNum个字母才能使每个字母满足给定范围的左边界
leftNum:放完这个字母后,原字符串中,当前位置后面还有leftNum个字母待考虑,待判断(在字典序最小的前提下)
needNum:放完这个字母后,还需要needNum个字母才能构成长度为k的目标子序列
(1)对于a->z的每个字母,已经放入的数目cnt和剩余待考虑的数目left,若:cnt+left < L || cnt > R (不要忘记判断cnt > R这种情况!!),则不可以放
(2)leastNum > needNum ,不可放
(3) needNum > leftNum,不可放
最后若构造完的字符串长度不为k,子序列无解,输出-1
若用字符数组存结果,且最后直接输出的话,最后一定要手动加一个'\0',否则就wa了
ac代码:
(写的不太优美,注释也有点乱ʕ •ᴥ•ʔ)
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100010;
int nxt[maxn][27], pos[27], nsum[maxn][27], l[27], r[27], cnt[27];
char s[maxn], ans[maxn];
int len, tot, k, now;
void getNxt()
{
len =strlen(s + 1);
memset(pos, -1, sizeof(pos));
memset(nsum, 0, sizeof(nsum));
for(int i = len; i >= 1; i--)//i最小到1, 因为0位置没有字符不能s[0]-'a'
{
for(int j = 0; j < 26; j++)
{
nxt[i][j] = pos[j];
nsum[i][j] = nsum[i + 1][j];
}
pos[s[i] - 'a'] = i;
nsum[i][s[i] - 'a']++;//nsum[i][j]记录i(包括i)后面j+'a'的数目
}
for(int i = 0; i < 26; i++)
nxt[0][i] = pos[i];
}
bool judge(int x)
{
if(x == -1)
return false;//now后面没有s[x]这个字母,now最大是len - 1
bool flag = true;
//假定可以放
cnt[s[x]-'a']++;
tot++;
//条件1,放了s[x]之后原串中剩余字母能否达到每个条件的左边界,或者已经超过了右边界
for(int i = 0; i < 26; i++)
if(nsum[x + 1][i] + cnt[i] < l[i] || cnt[i] > r[i])
{
flag = false;
break;
}
//条件2, leastNum 必须要 <= 构成目标串还需要的字符数k-tot
int leastNum = 0;//在加了当前这个字母的前提下,至少还需要添加leastNum个字母才能达到每个条件的左边界
for(int i = 0; i < 26; i++)
leastNum += max(l[i] - cnt[i], 0);
if(leastNum > k - tot)
flag = false;
//条件3 构成目标串还需要的字符数k-tot 必须要 <= 原串中剩余的字符数len-x
if(k - tot > len - x)
flag = false;
if(!flag)
{
tot--;
cnt[s[x] - 'a']--;
return false;
}
return true;
}
bool solve()
{
memset(cnt, 0, sizeof(cnt));
for(int i = 0; i < 26; i++)
if(nsum[1][i] < l[i])
return false;
now = 0, tot = 0;
while(now < len && tot < k)
{
int flag = 0;
for(int i = 0; i < 26; i++)
{
if(judge(nxt[now][i]))//可放
{
ans[tot-1] = i + 'a';//tot在judge函数中已经++了
now = nxt[now][i];
flag = 1;
break;//字典序最小
}
}
if(!flag)
return false;//当前位置没有合适的字符可以放, -1
}
if(tot != k) return false;
else return true;
}
int main()
{
//freopen("/Users/zhangkanqi/Desktop/11.txt","r",stdin);
ios::sync_with_stdio(false);
while(cin >> (s+1))
{
cin >> k;
for(int i = 0; i < 26; i++)
cin >> l[i] >> r[i];
getNxt();
if(solve())
{
ans[k] = '\0';//记得加!!!
cout << ans << endl;//这种输出方法比下面的快很多
/*for(int i = 0; i < k; i++)
cout << ans[i];
cout << endl;*/
}
else
cout << "-1" << endl;
}
return 0;
}