题目大意:给出一个字符串s[1],和一个数n,每次操作删除一个字母使s[i]字典序最小,得到新字符串s[i-1],直到得到长度为1的s[i]后,将s[i]字符串首尾相接得到字符串S,问S[n]
s[1]长度不超过1e6
思路:我们令S[n]所在的字符串为s[ans],那么s中s[ans]以外的字符串都是没有意义的,所以我们只要求s[1]删除ans-1个字母后的字符串中n对应位置N,因为s[i]每次长度-1,所以我们根据原来的n,可以循环求出ans和N。
然后考虑怎么删这ans个字母使字符串最小,先考虑删1个的时候,首先删靠前的字母肯定比删后边的字母好,也就是找最左边的能使字符串变小的位置,所以我们从前往后遍历,如果当前字母大于下一个字母,那么删掉这个字母就会使s[i+1]最小,然后同样再从前往后遍历s[i+1],找到第一个当前字母大于下一个字母的位置并删掉,可以得到s[i+2],如果不存在这样的位置,说明字符串此时是递增的,那么就从后往前逐个删即可。
但这样遍历ans次时间复杂度显然不能接受,通过进一步观察发现,我们要删ans个字母时,在找到一个当前字母大于下一个字母的位置时,要往前检查前面的字母是否也大于下一个字母,如果大于,前边的也要删,所以我们用栈去维护最终的s[ans],只要当前栈顶字母>当前字母,就将栈顶字母弹出,最终栈内的字母形成的字符串的前缀就是s[ans],这个字符串中的第N个字母就是答案,后缀是没有影响的,所以有时不用删完ans次。
//#include<__msvc_all_public_headers.hpp>
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MOD = 1e9 + 7;
const int N = 1e6+ 5;
ll n;
void init()
{
}
void solve()
{
string s;
cin >> s;
cin >> n;
init();
int cnt = 0;//要删几个字母
ll now = s.length();
while (n > now)
{//循环找到要删几个字母
n -= now;
now--;
cnt++;
}
string s2;
int l = 1;
vector<char>st;//用栈储存目标字符串
for (int i = 0; i < s.length(); i++)
{
while (!st.empty()&&st.back() > s[i]&&cnt)
{//栈顶比当前的大就弹出
st.pop_back();
cnt--;
}
st.push_back(s[i]);
}
cout << st[n-1];//vector完爆stack的地方
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int t;
cin >> t;
while (t--)
{
solve();
}
return 0;
}