考虑用两个线段树维护上一行以及当前行可达性。
分别存储每一行的炸弹的所有位置并排序,并且在两侧加上边界\(0\),\(m+1\)方便处理。
对于第一行,特殊判断:
\(\bullet\) 如果没有障碍物,那么所有点可达。
\(\bullet\) 如果有障碍物,那么第一个障碍物前可达。
随后遍历每一行,对于当前行,枚举每个障碍物,假设当前障碍物和上一个障碍物之间的区间为\([l,r]\),查询上一行当前区间可达的格子的最小下标\(x\),如果\(x\)存在的话,当前行\([x,r]\)都是可达的。然后不断枚举每一行并更新上一行及当前行可达状态。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN = 1e5 + 5;
const int INF = 0x3f3f3f3f;
struct SegmentTreeNode {
int l, r;
int val, add;
int mid() {
return (l + r) >> 1;
}
int len() {
return r - l + 1;
}
}tree[2][MAXN << 2];
#define ls(x) (x) << 1
#define rs(x) (x) << 1 | 1
void pushup(int rt, int p) {
tree[rt][p].val = tree[rt][ls(p)].val + tree[rt][rs(p)].val;
}
void pushdown(int rt, int p) {
if (tree[rt][p].add == -1) return ;
tree[rt][ls(p)].val = tree[rt][p].add * tree[rt][ls(p)].len();
tree[rt][rs(p)].val = tree[rt][p].add * tree[rt][rs(p)].len();
tree[rt][ls(p)].add = tree[rt][p].add;
tree[rt][rs(p)].add = tree[rt][p].add;
tree[rt][p].add = -1;
}
void build(int l, int r, int rt, int p) {
tree[rt][p].l = l, tree[rt][p].r = r;
tree[rt][p].add = -1, tree[rt][p].val = 0;
if (l == r) return ;
int mid = (l + r) >> 1;
build(l, mid, rt, ls(p));
build(mid + 1, r, rt, rs(p));
pushup(rt, p);
}
void update(int l, int r, int val, int rt, int p) {
if (l <= tree[rt][p].l && tree[rt][p].r <= r) {
tree[rt][p].val = val * tree[rt][p].len();
tree[rt][p].add = val;
return ;
}
pushdown(rt, p);
int mid = tree[rt][p].mid();
if (l <= mid) update(l, r, val, rt, ls(p));
if (r > mid) update(l, r, val, rt, rs(p));
pushup(rt, p);
}
int query(int l, int r, int rt, int p) {
if (tree[rt][p].val == 0) return INF;
if (tree[rt][p].l == tree[rt][p].r) {
return tree[rt][p].l;
}
pushdown(rt, p);
int mid = tree[rt][p].mid(), res = INF;
if (l <= mid) res = min(res, query(l, r, rt, ls(p)));
if (res == INF && r > mid) res = min(res, query(l, r, rt, rs(p)));
return res;
}
signed main(int argc, char *argv[]) {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int T;
cin >> T;
while (T--) {
int n, m, k;
cin >> n >> m >> k;
vector<int> pos[MAXN];
for (int i = 1; i <= n; i++) {
pos[i].push_back(0);
}
for (int i = 1; i <= k; i++) {
int x, y;
cin >> x >> y;
pos[x].push_back(y);
}
for (int i = 1; i <= n; i++) {
sort(pos[i].begin(), pos[i].end());
pos[i].push_back(m + 1);
}
int rt[2] = {0, 1};
build(1, m, 0, 1);
build(1, m, 1, 1);
int res = pos[1][1] - 1;
update(1, res, 1, rt[0], 1);
for (int i = 2; i <= n; ++i) {
update(1, m, 0, rt[1], 1);
for (int j = 1; j < pos[i].size(); ++j) {
int l = pos[i][j - 1] + 1, r = pos[i][j] - 1;
if (l > r) continue; // 如果两个障碍物间没有格子不处理
int x = query(l, r, rt[0], 1); // 查询上一行[l,r]可达点的最小下标
if (x == INT_MAX) continue;
update(x, r, 1, rt[1], 1);
}
res += tree[rt[1]][1].val; // 加上当前行可达点的贡献
swap(rt[0], rt[1]);
}
cout << res << '\n';
}
system("pause");
return 0;
}