#模拟
提供一种另类的模拟算法,适用于更多的情况(而不一定为 $\texttt{aba}$ 式,虽然赛后才发现最多只会删除两次)。
如果一次操作没有删除任何数的话,那么这次操作是毫无意义的,因此操作最多执行 $n$ 次。为了快速删除一个数,我们可以使用链表的结构。那么这样子的时间复杂度是 $\mathcal O(n^2)$ 的,仍然不优。
仔细观察,删除第 $i$ 个字符只会影响第 $i-1$ 个字符和第 $i+1$ 个字符,因此删除了之后直接对这两个进行判定就行了。但是我们需要考虑复杂一点的情况,假如有若干个需要删除的连续字符,那么我们只删除了一个就去对前一个字符作判定是不对的,因为后面的字符还没有删掉。
考虑新加一个 wait 数组,我们先把需要删的删掉了再来判断这些需要判断的数,而 wait 数组就是用来存储这些数的。这样,我们就可以愉快地使用 $\mathcal O(n)$ 的算法通过了。
```cpp
#include <iostream>
#include <vector>
using namespace std;
const int kMaxN = 1e7 + 5;
int prv[kMaxN], nxt[kMaxN], b[kMaxN], t, id, n; // prv 是上一个元素,nxt 是下一个,b 是标记数组
long long k; // 最多删除次数,注意使用 long long
vector<int> v, f, wait; // 分别为上一次删的,下一步要删的和 wait 数组
char s[kMaxN]; // 用来存储字符串
// 判断第 x 个字符是否满足条件
bool check(int x) { return s[nxt[x]] == s[prv[x]]; }
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); // 读入优化
for (cin >> t >> id; t; t--) { // 多组数据
cin >> n >> k >> (s + 1); // 输入信息
f.clear(); // 清空第一次要删的
// 初始化链表
for (int i = 1; i <= n; i++) {
prv[i] = i - 1, nxt[i] = i + 1;
}
// 第一次删除(没有真正删掉)
for (int i = 2; i <= n; i++) {
check(i) && (f.push_back(i), 0);
}
// 最多 k 次并且有要删的
for (int i = 1; i <= k && f.size(); i++) {
wait.clear(); // 清空 wait 数组
for (int j : f) { // 遍历要删的
b[j] = 1; // 真正删除
}
// 执行下一步删除
for (int j = 0, k; j < f.size(); ) {
for (k = j + 1; k < f.size() && f[k] - f[k - 1] == 1; k++) { } // 找到一段连续的 [j, k)
nxt[prv[f[j]]] = nxt[f[k - 1]]; // 对链表进行删除操作
prv[nxt[f[k - 1]]] = prv[f[j]];
wait.push_back(prv[f[j]]); // 加入 wait 数组
wait.push_back(nxt[f[k - 1]]);
j = k; // 跳过去
}
// 对 wait 进行逐一排查
for (int j : wait) {
if (!b[j] && 1 < j && j < n && check(j)) { // 如果满足条件
v.push_back(j); // 加入下一步要删的
}
}
f = v, v.clear(); // 交换并清空
}
// 输出并清空
for (int i = 1; i <= n; b[i++] = 0) {
if (!b[i]) {
cout << s[i];
}
}
cout << '\n';
}
return 0;
}
```