例题
维护一个序列,支持以下 3 3 3 种操作:
-
在序列末尾插入元素;
-
撤销若干次 1 1 1 和 2 2 2 操作;
-
查询序列某个位置的数。
T T T 组数据, T ≤ 5 T \leq 5 T≤5,
对于 n n n 次操作, n ≤ 2 × 1 0 5 n \leq 2 \times 10 ^ 5 n≤2×105,输出 3 3 3 操作的结果。
分析
直接模拟,维护各个历史版本的序列,复杂度是 O ( n 2 ) O(n ^ 2) O(n2) 的;
将插入数作为节点建成操作树,记录各次操作后的活动节点(初始为根节点),
撤销操作则对应活动节点的改动,
活动节点向上到根的路径上的节点(根节点除外)则构成当前序列,
每次查询相当于求指定深度的点,记录每个节点深度,用树上倍增即可得到。
参考代码
#include <cstdio>
inline int read() {
int num = 0;
char c = getchar();
while (c < '0' || c > '9') c = getchar();
while (c >= '0' && c <= '9')
num = num * 10 + c - '0', c = getchar();
return num;
}
const int maxn = 2e5 + 5;
int seq[maxn], tot, f[maxn][20], depth[maxn], v[maxn];
inline void add(int id, int x) {
seq[++tot] = id, v[id] = x;
f[id][0] = seq[tot - 1], depth[id] = depth[f[id][0]] + 1;
for (int i = 1; i < 20; ++i) {
if ((1 << i) > depth[id]) break;
f[id][i] = f[f[id][i - 1]][i - 1];
}
}
inline int query(int x) {
int p = seq[tot];
x = depth[p] - x;
for (int i = 20; i >= 0; --i)
if ((1 << i) <= x) x -= (1 << i), p = f[p][i];
return v[p];
}
int main() {
int t = read();
while (t--) {
seq[0] = 0, tot = 0, depth[0] = 0;
int n = read();
for (int i = 1; i <= n; ++i) {
int op = read(), x = read();
if (op == 1) add(i, x);
else if (op == 2) ++tot, seq[tot] = seq[tot - 1 - x];
else if (op == 3) printf("%d\n", query(x));
}
}
return 0;
}