1 条题解

  • 0
    @ 2026-10-27 8:51:00

    思路与算法

    正序放棋会把3类道路的空格连通块拆开,不便维护;倒序移走棋子则只会合并空格。先将所有询问的棋子放上,再按q到1移走当前棋子。移走i并处理完后,棋盘恰为第i次落子之前;将当前位置当作出发点,统计可到达的其他格子。 每个3路空格并查集分量维护四棵可合并动态线段树:行主序位置、列主序位置,以及黑白两色的边界棋集合。先把行列减1,行主序编号为x·m+y,列主序为y·n+x。空格树叶值为1;边界棋按(等级,落子编号)排序的唯一键编号,叶值也只为0或1。同一棋子从多条3路接触同一分量仍只记一次,合并叶子时取逻辑或。颜色分树后,对异色树作等级前缀计数就得到3路可吃的棋数。 倒序移走一枚棋时,为新空格建立位置树,合并所有3路空邻居分量,把它周围尚占据的3路邻居加入边界集合,并从合并后的边界集合删除刚移走棋子的唯一键。这样3路贡献就是分量大小减1,再加异色且等级不高于当前棋的边界棋数。 2路不能转弯。预处理每格横、竖方向连续2路段的两端;用两个有序集合维护行主序、列主序的占据位置。对出发点求前驱后继,截出被最近棋子挡住的空区间。横向用行位置树查询该区间已经属于3路分量的空格数,竖向用列位置树查询,区间长度减去此数就是新增贡献。最近阻挡棋若位于连续2路范围内、可吃且不在3路边界集合中,再加1。最后直接检查1路邻格:空格若已在3路分量就跳过,棋子若已在3路边界可吃集合也跳过。按原询问次序输出。

    正确性说明

    倒序状态不变量:处理完i后,尚占据的位置恰为编号小于i的棋,所有空格按3路连通关系构成并查集分量;每个分量的两棵位置树恰含它的全部空格,边界树恰含与它至少有一条3路相接的尚占据棋,每枚棋至多一次。初始化直接建立所有最终空格、合并3路空边并收集占据邻棋,满足不变量。移走i时,只新增当前空点和与它相接的空边,故合并空邻居完整刻画新连通块。原边界的并集再加入当前点的占据邻居,正好是新边界;i已为空且其所有空邻分量都已合并,删除它的键即可。其他分量不受影响。因此不变量归纳成立。 3路移动不能穿过占据格,全部可达空格正是含起点分量中除起点外的格子。所有可吃终点恰为异色、等级合格的边界棋;合并时叶取布尔或,保证多处接触同一棋也只计一个物理位置。 2路的每个方向必须沿同一行或列的连续2边行进,并在第一个占据格处终止。预处理段端点和占据前驱后继的截断恰给出全部可走空格;位置树范围计数恰删除其中已由3路到达的格子,起点包含在区间和位置树中,二者相减也自动去掉起点。横竖区间除起点外不交,故不会互相重复;横竖阻挡棋也不可能是同一格。1路只有邻格一步,若能同时由2路直行到达这个相邻格,就必须让同一条边同时为1和2,矛盾,因此1路与2路之间无需扣重,剩下只需按上述集合检查它与3路的重叠。所有贡献对应合法单次移动,且覆盖所有允许的道路类型,故总数正确。

    复杂度

    记N=n·m。读图、预处理四个2路端点与初始化并查集O(N),排序等级和建立占据有序集合O(Qlog Q)。每个格子只在变空时向两棵位置树各插入一次。某枚棋的边界插入只可能来自与它相接的至多4条3路边,每条边在另一端变空时贡献一次,因此全部边界插入次数不超过4Q。每次点操作O(log N),总开点数至多(2N+4Q)(ceil(log₂N)+1)+1。 线段树合并中,每次两个非空树结点相遇都会让其中一个结点退出活结构,退出结点不再重新出现,所以全部合并总访问量由总开点数线性界定。每个询问常数次范围计数、边界键查询和占据前驱后继操作,均O(log N)。总时间O((N+Q)log N),空间O((N+Q)log N)。满N=200000、Q=100000时预留15200001个三整数结点,节点池约174MiB;其余位置、并查集、等级、有序集合仍为线性空间。节点池一次reserve,避免扩容时旧新数组同时驻留。

    易错点

    不能混用不同道路,也不能在吃子后继续行进。边界按唯一落子编号而非只按等级去重,同级不同棋必须分别计数;同一棋多处接触则只计一次。逆序删除当前棋的边界键不可遗漏,否则会留下已不存在的棋。纵向区间要在列主序中查询,行主序不能直接表达一列的连续区间。2路最近棋子即使不可吃也会挡路,不能继续找下一枚。起点不是合法目标。

    参考代码(C++11)

    #include <bits/stdc++.h>
    using namespace std;
    struct Node {
        int left = 0, right = 0, sum = 0;
    };
    struct SegmentPool {
        int n;
        vector<Node> tree;
        SegmentPool(int count, int q) : n(count) {
            int depth = 0;
            while ((1 << depth) < n)
                depth++;
            tree.reserve((2ULL * n + 4ULL * q) * (depth + 1) + 1);
            tree.push_back(Node());
        }
        int change(int root, int left, int right, int position, int value) {
            if (!root) {
                if (!value)
                    return 0;
                root = tree.size();
                tree.push_back(Node());
            }
            if (left == right) {
                tree[root].sum = value;
                return root;
            }
            int middle = (left + right) / 2;
            if (position <= middle) {
                int child = change(tree[root].left, left, middle, position, value);
                tree[root].left = child;
            } else {
                int child = change(tree[root].right, middle + 1, right, position, value);
                tree[root].right = child;
            }
            tree[root].sum = tree[tree[root].left].sum + tree[tree[root].right].sum;
            return root;
        }
        int merge(int a, int b, int left, int right) {
            if (!a || !tree[a].sum)
                return b;
            if (!b || !tree[b].sum)
                return a;
            if (left == right) {
                tree[a].sum = 1;
                return a;
            }
            int middle = (left + right) / 2;
            int l = merge(tree[a].left, tree[b].left, left, middle);
            int r = merge(tree[a].right, tree[b].right, middle + 1, right);
            tree[a].left = l;
            tree[a].right = r;
            tree[a].sum = tree[l].sum + tree[r].sum;
            return a;
        }
        int query(int root, int left, int right, int low, int high) const {
            if (!root || !tree[root].sum || low > high)
                return 0;
            if (low <= left && right <= high)
                return tree[root].sum;
            int middle = (left + right) / 2, answer = 0;
            if (low <= middle)
                answer += query(tree[root].left, left, middle, low, high);
            if (high > middle)
                answer += query(tree[root].right, middle + 1, right, low, high);
            return answer;
        }
        bool contains(int root, int position) const {
            int left = 0, right = n - 1;
            while (root && tree[root].sum && left < right) {
                int middle = (left + right) / 2;
                if (position <= middle) {
                    root = tree[root].left;
                    right = middle;
                } else {
                    root = tree[root].right;
                    left = middle + 1;
                }
            }
            return root && tree[root].sum;
        }
        int change(int root, int position, int value) {
            return change(root, 0, n - 1, position, value);
        }
        int merge(int a, int b) {
            return merge(a, b, 0, n - 1);
        }
        int query(int root, int low, int high) const {
            return query(root, 0, n - 1, low, high);
        }
    };
    struct Board {
        int n, m, q, N;
        vector<unsigned char> horizontal, vertical;
        vector<int> occupied, color, level, position, key, levelCut;
        vector<int> parent, size, rowTree, columnTree;
        vector<array<int, 2>> boundary;
        vector<int> horizontalLeft, horizontalRight, verticalTop, verticalBottom;
        unique_ptr<SegmentPool> pool;
        set<int> rowOccupied, columnOccupied;
        int find(int u) {
            int r = u;
            while (parent[r] != r)
                r = parent[r];
            while (parent[u] != u) {
                int next = parent[u];
                parent[u] = r;
                u = next;
            }
            return r;
        }
        int unite(int u, int v) {
            u = find(u);
            v = find(v);
            if (u == v)
                return u;
            if (size[u] < size[v])
                swap(u, v);
            parent[v] = u;
            size[u] += size[v];
            rowTree[u] = pool->merge(rowTree[u], rowTree[v]);
            columnTree[u] = pool->merge(columnTree[u], columnTree[v]);
            for (int c = 0; c < 2; c++)
                boundary[u][c] = pool->merge(boundary[u][c], boundary[v][c]);
            return u;
        }
        int columnIndex(int u) const {
            return (u % m) * n + u / m;
        }
        int rowIndex(int v) const {
            return (v % n) * m + v / n;
        }
        template <class F> void neighbors(int u, F f) const {
            int row = u / m, col = u % m;
            if (col + 1 < m)
                f(u + 1, horizontal[u]);
            if (col)
                f(u - 1, horizontal[u - 1]);
            if (row + 1 < n)
                f(u + m, vertical[u]);
            if (row)
                f(u - m, vertical[u - m]);
        }
        void read() {
            cin >> n >> m >> q;
            N = n * m;
            horizontal.assign(N, 0);
            vertical.assign(N, 0);
            string s;
            for (int x = 0; x < n; x++) {
                cin >> s;
                for (int y = 0; y < m - 1; y++)
                    horizontal[x * m + y] = s[y] - '0';
            }
            for (int x = 0; x < n - 1; x++) {
                cin >> s;
                for (int y = 0; y < m; y++)
                    vertical[x * m + y] = s[y] - '0';
            }
            occupied.assign(N, 0);
            color.resize(q + 1);
            level.resize(q + 1);
            position.resize(q + 1);
            key.resize(q + 1);
            levelCut.resize(q + 1);
            vector<pair<int, int>> order(q);
            for (int i = 1, x, y; i <= q; i++) {
                cin >> color[i] >> level[i] >> x >> y;
                --x;
                --y;
                position[i] = x * m + y;
                occupied[position[i]] = i;
                order[i - 1] = {level[i], i};
                rowOccupied.insert(position[i]);
                columnOccupied.insert(columnIndex(position[i]));
            }
            sort(order.begin(), order.end());
            vector<int> sortedLevel(q);
            for (int j = 0; j < q; j++) {
                key[order[j].second] = j;
                sortedLevel[j] = order[j].first;
            }
            for (int i = 1; i <= q; i++)
                levelCut[i] = upper_bound(sortedLevel.begin(), sortedLevel.end(), level[i]) -
                              sortedLevel.begin() - 1;
            horizontalLeft.resize(N);
            horizontalRight.resize(N);
            verticalTop.resize(N);
            verticalBottom.resize(N);
            for (int u = 0; u < N; u++) {
                horizontalLeft[u] = (u % m && horizontal[u - 1] == 2) ? horizontalLeft[u - 1] : u;
                verticalTop[u] = (u >= m && vertical[u - m] == 2) ? verticalTop[u - m] : columnIndex(u);
            }
            for (int u = N - 1; u >= 0; u--) {
                horizontalRight[u] = (u % m + 1 < m && horizontal[u] == 2) ? horizontalRight[u + 1] : u;
                verticalBottom[u] =
                    (u + m < N && vertical[u] == 2) ? verticalBottom[u + m] : columnIndex(u);
            }
            parent.resize(N);
            iota(parent.begin(), parent.end(), 0);
            size.assign(N, 0);
            rowTree.assign(N, 0);
            columnTree.assign(N, 0);
            boundary.resize(N);
            pool.reset(new SegmentPool(N, q));
            for (int u = 0; u < N; u++)
                if (!occupied[u]) {
                    size[u] = 1;
                    rowTree[u] = pool->change(0, u, 1);
                    columnTree[u] = pool->change(0, columnIndex(u), 1);
                }
            for (int u = 0; u < N; u++)
                if (!occupied[u]) {
                    if (u % m + 1 < m && horizontal[u] == 3 && !occupied[u + 1])
                        unite(u, u + 1);
                    if (u + m < N && vertical[u] == 3 && !occupied[u + m])
                        unite(u, u + m);
                }
            for (int u = 0; u < N; u++)
                if (occupied[u]) {
                    int piece = occupied[u];
                    neighbors(u, [&](int v, int kind) {
                        if (kind == 3 && !occupied[v]) {
                            int root = find(v);
                            boundary[root][color[piece]] =
                                pool->change(boundary[root][color[piece]], key[piece], 1);
                        }
                    });
                }
        }
        vector<int> solve() {
            vector<int> answer(q);
            for (int i = q; i >= 1; i--) {
                int u = position[i], column = columnIndex(u);
                occupied[u] = 0;
                parent[u] = u;
                size[u] = 1;
                rowTree[u] = pool->change(0, u, 1);
                columnTree[u] = pool->change(0, column, 1);
                boundary[u] = {0, 0};
                neighbors(u, [&](int v, int kind) {
                    if (kind == 3 && !occupied[v])
                        unite(u, v);
                });
                int root = find(u);
                neighbors(u, [&](int v, int kind) {
                    if (kind == 3 && occupied[v]) {
                        int piece = occupied[v];
                        boundary[root][color[piece]] =
                            pool->change(boundary[root][color[piece]], key[piece], 1);
                    }
                });
                boundary[root][color[i]] = pool->change(boundary[root][color[i]], key[i], 0);
                rowOccupied.erase(u);
                columnOccupied.erase(column);
                int opposite = color[i] ^ 1;
                int result = size[root] - 1 + pool->query(boundary[root][opposite], 0, levelCut[i]);
                auto capture = [&](int v) {
                    int piece = occupied[v];
                    return piece && color[piece] != color[i] && level[piece] <= level[i] &&
                           !pool->contains(boundary[root][opposite], key[piece]);
                };
                int left = -1, right = N;
                auto it = rowOccupied.lower_bound(u);
                if (it != rowOccupied.end())
                    right = *it;
                if (it != rowOccupied.begin())
                    left = *prev(it);
                int low = max(horizontalLeft[u], left + 1), high = min(horizontalRight[u], right - 1);
                result += high - low + 1 - pool->query(rowTree[root], low, high);
                if (left >= horizontalLeft[u] && capture(left))
                    ++result;
                if (right <= horizontalRight[u] && capture(right))
                    ++result;
                left = -1;
                right = N;
                it = columnOccupied.lower_bound(column);
                if (it != columnOccupied.end())
                    right = *it;
                if (it != columnOccupied.begin())
                    left = *prev(it);
                low = max(verticalTop[u], left + 1);
                high = min(verticalBottom[u], right - 1);
                result += high - low + 1 - pool->query(columnTree[root], low, high);
                if (left >= verticalTop[u] && capture(rowIndex(left)))
                    ++result;
                if (right <= verticalBottom[u] && capture(rowIndex(right)))
                    ++result;
                neighbors(u, [&](int v, int kind) {
                    if (kind != 1)
                        return;
                    if (!occupied[v]) {
                        if (find(v) != root)
                            ++result;
                    } else if (capture(v))
                        ++result;
                });
                assert(0 <= result && result < N);
                answer[i - 1] = result;
            }
            return answer;
        }
    };
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        int T;
        cin >> T;
        while (T--) {
            Board board;
            board.read();
            auto answer = board.solve();
            for (int x : answer)
                cout << x << '\n';
        }
    }
    
    • 1

    信息

    ID
    2058
    时间
    4000ms
    内存
    512MiB
    难度
    省选
    标签
    递交数
    0
    已通过
    0
    上传者