显然可以 \(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]);
}