C. Decreasing String
C. Decreasing String
Recall that string is lexicographically smaller than string if is a prefix of (and ), or there exists an index () such that , and for any index () .
Consider a sequence of strings , each consisting of lowercase Latin letters. String is given explicitly, and all other strings are generated according to the following rule: to obtain the string , a character is removed from string in such a way that string is lexicographically minimal.
For example, if , then string , string , string .
After that, we obtain the string ( is the concatenation of all strings ).
You need to output the character in position of the string (i. e. the character ).
Input
The first line contains one integer — the number of test cases ().
Each test case consists of two lines. The first line contains the string (), consisting of lowercase Latin letters. The second line contains the integer (). You may assume that is equal to the length of the given string ().
Additional constraint on the input: the sum of over all test cases does not exceed .
Output
For each test case, print the answer — the character that is at position in string . Note that the answers between different test cases are not separated by spaces or line breaks.
Example
input
3
cab
6
abcd
9
x
1
output
abx
解题思路
我的做法跟官方的不一样,先给出我的思路。
对于给定 ,假设其表示第 个子串 中的 第 个字符,那么问题就等价于在 中删除 个字符后所得到的字符串中,字典序最小的字符串的第 个字符是什么。而题目有限制 的获得是通过删除 的一个字符所得到的所有字符串中字典序最小的那个,因此我们还需要证明通过这个限制得到的 一定是从 中删除 个字符后得到的字典序最小的字符串,才能保证上面的问题与原问题是等价的。
归纳法,显然 是从 中删除 个字符后得到的字典序最小的字符串。假设 仍然满足这个条件,由于 是从 中删除 个字符后得到的字典序最小的字符串,而 是从 中删除一个字符得到的字典序最小的字符串,因此 一定也是从 中删除 字符得到的字典序最小的字符串。
而从 中删除 个字符后所得到的字典序最小的字符串,还可以等价于从 中选择 个字符所构成的字典序最小的字符串。因此要在 的 中选择 个字符,那么对于 的第 个字符串,必然是从 的 中选择最靠左且最小的字符 (否则如果选择的 有 ,那么不可能选到 个字符,因为 )。同理,此时要在 的 中选择 个字符,那么对于 的第 个字符串,必然是从 的 中选择最靠左且最小的字符 ,以此类推。当选择完第 个字符时,那么这个字符就是 中的第 个字符。
可以发现上面的过程本质就是求某个区间的最小值,可以用线段树来维护。
AC 代码如下,时间复杂度为 :
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
char s[N];
struct Node {
int l, r, mn;
}tr[N * 4];
void build(int u, int l, int r) {
tr[u] = {l, r};
if (l == r) {
tr[u].mn = l;
}
else {
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
if (s[tr[u << 1].mn] <= s[tr[u << 1 | 1].mn]) tr[u].mn = tr[u << 1].mn;
else tr[u].mn = tr[u << 1 | 1].mn;
}
}
int query(int u, int l, int r) {
if (tr[u].l >= l && tr[u].r <= r) return tr[u].mn;
int mid = tr[u].l + tr[u].r >> 1;
if (r <= mid) return query(u << 1, l, r);
else if (l > mid) return query(u << 1 | 1, l, r);
int x = query(u << 1, l, r), y = query(u << 1 | 1, l, r);
if (s[x] <= s[y]) return x;
return y;
}
void solve() {
LL m;
scanf("%s %lld", s + 1, &m);
int n = strlen(s + 1), k;
for (int i = 1; i <= n; i++) { // 求出S的第m个字符是在哪个子串
if (m <= n - i + 1) {
k = i;
break;
}
m -= n - i + 1;
}
build(1, 1, n);
for (int i = 1, j = 0; i <= n - k + 1; i++) {
j = query(1, j + 1, k + i - 1); // 求出这个区间内最靠左且最小的字符
if (i == m) {
printf("%c", s[j]);
return;
}
}
}
int main() {
int t;
scanf("%d", &t);
while (t--) {
solve();
}
return 0;
}
下面给出官方的做法。
考虑应该删除 中的哪个字符,使得 的字典序最小。可以证明应该选择最靠左第一个满足 的字符 ,因为删除后得到的 会变小(即变成了 )。假设删除的是 中的字符,那么得到的 ,字典序不会变小。假设删除的是 中的字符,由于有 ,那么得到的 的前 个字符中可能存在某个字符大于 中与之对应的字符,而第 个位置后的字符都相同,因此 的字典序不可能比 小。得证。
为此我们可以从左到右依次枚举 中的字符并用栈来维护,找到所有最靠左第一个满足 的字符 。当枚举到 ,此时如果栈不为空且栈顶元素大于 ,那么就将栈顶元素弹出,表示删除了满足条件的字符,不断进行这个过程,直到栈为空或者栈顶元素不超过 ,然后再把 压入栈。因此从栈底到栈顶元素满足非递降的关系。
当我们从栈中弹出了 个元素(这里的 同上面方法的 ),则表示已经删除了 个元素,此时只需把剩余的字符都压入栈,那么栈中第 个元素就是答案。如果弹出的元素个数小于 ,为了得到字典序最小的字符串,应该继续从栈顶弹出直到达到 个,然后栈顶元素就是答案(其实就是栈中第 个元素)。
AC 代码如下,时间复杂度为 :
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
char s[N];
char stk[N];
void solve() {
LL m;
scanf("%s %lld", s, &m);
int n = strlen(s), k;
for (int i = 0; i < n; i++) {
if (m <= n - i) {
k = i;
break;
}
m -= n - i;
}
int tp = 0;
for (int i = 0; i < n; i++) {
while (k && tp && stk[tp] > s[i]) {
tp--;
k--;
}
stk[++tp] = s[i];
}
printf("%c", stk[m]);
}
int main() {
int t;
scanf("%d", &t);
while (t--) {
solve();
}
return 0;
}
参考资料
Educational Codeforces Round 156 Editorial:https://codeforces.com/blog/entry/121255
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/17763323.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
2022-10-13 E. Sending a Sequence Over the Network