Lawn of the Dead

考虑用两个线段树维护上一行以及当前行可达性。

分别存储每一行的炸弹的所有位置并排序,并且在两侧加上边界\(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;
}