C. Decreasing String

C. Decreasing String

Recall that string a is lexicographically smaller than string b if a is a prefix of b (and ab), or there exists an index i (1imin(|a|,|b|)) such that ai<bi, and for any index j (1j<i) aj=bj.

Consider a sequence of strings s1,s2,,sn, each consisting of lowercase Latin letters. String s1 is given explicitly, and all other strings are generated according to the following rule: to obtain the string si, a character is removed from string si1 in such a way that string si is lexicographically minimal.

For example, if s1=dacb, then string s2=acb, string s3=ab, string s4=a.

After that, we obtain the string S=s1+s2++sn (S is the concatenation of all strings s1,s2,,sn).

You need to output the character in position pos of the string S (i. e. the character Spos).

Input

The first line contains one integer t — the number of test cases (1t104).

Each test case consists of two lines. The first line contains the string s1 (1|s1|106), consisting of lowercase Latin letters. The second line contains the integer pos (1pos|s1|(|s1|+1)2). You may assume that n is equal to the length of the given string (n=|s1|).

Additional constraint on the input: the sum of |s1| over all test cases does not exceed 106.

Output

For each test case, print the answer — the character that is at position pos in string S. 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

 

解题思路

  我的做法跟官方的不一样,先给出我的思路。

  对于给定 m,假设其表示第 k 个子串 sk 中的 第 x 个字符,那么问题就等价于在 s1 中删除 k1 个字符后所得到的字符串中,字典序最小的字符串的第 x 个字符是什么。而题目有限制 si 的获得是通过删除 si1 的一个字符所得到的所有字符串中字典序最小的那个,因此我们还需要证明通过这个限制得到的 sk 一定是从 s1 中删除 k1 个字符后得到的字典序最小的字符串,才能保证上面的问题与原问题是等价的。

  归纳法,显然 s2 是从 s1 中删除 1 个字符后得到的字典序最小的字符串。假设 sk1 仍然满足这个条件,由于 sk1 是从 s1 中删除 k2 个字符后得到的字典序最小的字符串,而 sk 是从 sk1 中删除一个字符得到的字典序最小的字符串,因此 sk 一定也是从 s1 中删除 k1 字符得到的字典序最小的字符串。

  而从 s1 中删除 k1 个字符后所得到的字典序最小的字符串,还可以等价于从 s1 中选择 nk+1 个字符所构成的字典序最小的字符串。因此要在 s1[1,n] 中选择 nk+1 个字符,那么对于 sk 的第 1 个字符串,必然是从 s1i[1,k] 中选择最靠左且最小的字符 s1,i(否则如果选择的 s1,ii>k,那么不可能选到 nk+1 个字符,因为 ni+1<nk+1)。同理,此时要在 s1[i+1,n] 中选择 nk 个字符,那么对于 sk 的第 2 个字符串,必然是从 s1j[i+1,k+1] 中选择最靠左且最小的字符 s1,j,以此类推。当选择完第 x 个字符时,那么这个字符就是 S 中的第 m 个字符。

  可以发现上面的过程本质就是求某个区间的最小值,可以用线段树来维护。

  AC 代码如下,时间复杂度为 O(nlogn)

#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;
}

  下面给出官方的做法。

  考虑应该删除 s1 中的哪个字符,使得 s2 的字典序最小。可以证明应该选择最靠左第一个满足 s1,i>s1,i+1 的字符 s1,i,因为删除后得到的 s2,i 会变小(即变成了 s1,i+1)。假设删除的是 [i+1,n] 中的字符,那么得到的 s2,i=s1,i>s2,i=s1,i+1,字典序不会变小。假设删除的是 [1,i1] 中的字符,由于有 s1,1s1,2s1,i,那么得到的 s2 的前 i1 个字符中可能存在某个字符大于 s2 中与之对应的字符,而第 i 个位置后的字符都相同,因此 s2 的字典序不可能比 s2 小。得证。

  为此我们可以从左到右依次枚举 s1 中的字符并用栈来维护,找到所有最靠左第一个满足 s1,i>s1,i+1 的字符 s1,i。当枚举到 s1,i,此时如果栈不为空且栈顶元素大于 s1,i,那么就将栈顶元素弹出,表示删除了满足条件的字符,不断进行这个过程,直到栈为空或者栈顶元素不超过 s1,i,然后再把 s1,i 压入栈。因此从栈底到栈顶元素满足非递降的关系。

  当我们从栈中弹出了 k1 个元素(这里的 k 同上面方法的 k),则表示已经删除了 k1 个元素,此时只需把剩余的字符都压入栈,那么栈中第 x 个元素就是答案。如果弹出的元素个数小于 k1,为了得到字典序最小的字符串,应该继续从栈顶弹出直到达到 k1 个,然后栈顶元素就是答案(其实就是栈中第 x 个元素)。

  AC 代码如下,时间复杂度为 O(n)

#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

posted @   onlyblues  阅读(58)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
历史上的今天:
2022-10-13 E. Sending a Sequence Over the Network
Web Analytics
点击右上角即可分享
微信分享提示