CF1886C Decreasing String

发布时间 2023-10-10 20:11:18作者: yxu0528

单调栈的应用。

显然可以 \(O(n)\) 地找到 \(pos\) 所属的 \(s_i\) 段,所以我们只需要得到 \(s_i\) 即可。不难发现,删除元素的规则应该是从 \(1\)\(n\) 枚举每个元素,删除它前面“紧邻的”比他大的元素(例如对于 eadcb 中的 b 删除掉 dc)。

赛时一直在想,怎么快速找到一个元素前面这些“紧邻的”比它大的元素,但这实际上根本不需要处理——删除掉这些组成逆序对的元素,前面的元素显然就有序了!也就是说,我们在维护一个单调栈,每次入栈 \(s_i\) 时,弹掉栈顶那些比它大的元素,再压入 \(s_i\)

需要注意的是,我们弹掉栈顶的次数是有限制的,要恰好弹掉 \(n-len(s_i)\) 个元素。弹完这些之后,后面即使栈内元素不单调了,也不能再弹了;如果还没弹够这个数目就完事了,则按理说要继续弹掉最后加进去的元素(按照题目字典序定义 1),但在实现中,因为 \(pos\) 不会取到后面的元素,弹不弹没有太大影响。

赛时思维固定在“如何找”上,没有考虑到“不用找”的问题,导致无法破题;在设计不出想要的复杂度的算法时,应当手工模拟一下运算过程,注意观察数据特点,可能有利于突破固有思路。

下面是 AC 代码:

void solve() {
    scanf("%s", s + 1);
    int n = strlen(s + 1), tot = 0, len;
    i64 pos;
    scanf("%lld", &pos); // 注意!题面中故意没有给出明确数据,导致 WA on test 8
    for (len = n; len >= 1; len--) {
        if (pos - len <= 0) {
            break;
        }
        pos -= len;
    }
    int cnt = 0;
    for (int i = 1; i <= n; i++) {
        while (cnt < n - len && tot && st[tot] > s[i]) { // 还有次数->栈不为空->不单调
            tot--, cnt++;
        }
        st[++tot] = s[i];
    }
    putchar(st[pos]);
}

THE END