从位置 u 开始,若下一张同点牌在区间内,就整段消掉并跳到 nxt[u]+1。
P10264 [GESP202403 八级] 接竹竿
GESP8 冲刺课堂页:先建模,再压缩状态,最后读代码。
一、先回答问题
每个询问都完整模拟队列,q*n 会超时。
二、核心观察
nxt[i] 是下一张同点牌;jump[i][k] 表示连续做 2^k 次消除跳跃后的位置。
为什么不丢答案:当当前牌在队底时,它只会被下一张同点牌消掉,中间牌一起清空。
u
nxt[u]
jump[u][k]
三、互动演示
步骤1
关键状态 A-
关键状态 B-
答案/目标-
复杂度看表
课堂动作手推
点击下一步开始。
四、自己选答案
剩余牌为什么最多和点数种类有关?
先判断,再点按钮。
jump 合法条件是什么?
先判断,再点按钮。
五、手推结果
| 情况 | 动作 | 答案 |
|---|---|---|
| nxt[u]<=r | 跳到 nxt[u]+1 | 不变 |
| nxt[u]>r | u 留下,u++ | +1 |
| 多次消除 | 倍增跳 | 加速 |
课堂要求:每题先说清对象、关系、保留的信息、压缩掉的信息,再写代码。
六、C++14 参考代码
#include <bits/stdc++.h>
using namespace std;
void solve() {
int n;
cin >> n;
vector<int> a(n + 1);
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
int q;
cin >> q;
int max_jump = n + 2;
vector<int> nxt(n + 1, max_jump);
vector<int> last_occ(15, max_jump); // 点数最多 13
for (int i = n; i >= 1; i--) {
nxt[i] = last_occ[a[i]];
last_occ[a[i]] = i;
}
// jump1[i][k] 表示从 i 开始进行 2^k 次 "消除跳跃" 到达的位置
vector<vector<int>> jump1(max_jump + 1, vector<int>(15, max_jump));
for (int i = 1; i <= n; i++) {
if (nxt[i] <= n) {
jump1[i][0] = nxt[i] + 1;
} else {
jump1[i][0] = max_jump;
}
}
for (int k = 1; k < 15; k++) {
for (int i = 1; i <= max_jump; i++) {
jump1[i][k] = jump1[jump1[i][k - 1]][k - 1];
}
}
while (q--) {
int l, r;
cin >> l >> r;
int u = l;
int ans = 0;
while (u <= r) {
// 利用倍增快速做 "消除跳跃"
for (int k = 14; k >= 0; k--) {
// 如果跳跃后的位置在 r+1 以内,说明所有的消除都合法
if (jump1[u][k] <= r + 1) {
u = jump1[u][k];
}
}
// 跳不动了,说明当前牌 A_u 的下一次出现超出了 r
// 这张牌永远不会被消除,答案 +1
if (u <= r) {
ans++;
u++;
}
}
cout << ans << "\n";
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
if (cin >> t) {
while (t--) {
solve();
}
}
return 0;
}