P8276 [USACO22OPEN] Hoof and Brain P

· · 题解

首先可以注意到,如果某个棋子所在的点没有出边,那么甲只需要选择这个棋子就能够获胜。

于是我们可以将这些点标记,然后将它们从原图中删除。但在完成删除操作后,可能又会有新的节点出边数量变成了 0,我们需要重复这样的删除操作只到图中任意节点都有至少 1 条出边。

现在我们考虑一个只有 1 条出边的节点 x,假设这条边指向的节点是 y,那么对于任意 x 上的棋子,移动后都一定会到达 y。如果甲能够通过让两个棋子都移动到 x 获胜,那么甲显然也能够通过让两个棋子都移动到 y 获胜。于是我们可以将 xy 合并,具体来说,我们对于每条边 z \to x,将其删除,然后若 z \to y 不存在则加入边 z \to y。和之前相同,此时可能又会有新的节点出边数量变为 1,我们需要将它们继续合并。

合并后,新图中的所有点都有至少 2 条出边,或有一个自环。先考虑所有点都至少有 2 条出边的情况,显然乙的每次操作都能够避免使两个棋子进入同一个节点。而有自环的情况同样平凡。

于是,对于询问 (x,y),我们可以按照如下方式判定胜负:

xy 被删除,那么甲必胜。否则,如果 xy 被合并成了一个节点,那么甲必胜,否则乙必胜。

使用启发式合并 set 维护每个节点的出边集合 f 和入边集合 g,并查集维护合并后每个点所在的集合,时间复杂度 \mathcal{O}(m \log n + q)

#include <bits/stdc++.h>
#define ins insert
#define era erase
using namespace std;
const int N = 2e5 + 5;
int n, m, k, fa[N];
set <int> f[N], g[N]; // f : out, g : in
queue <int> q;
int gf(int x) {
    return x == fa[x] ? x : fa[x] = gf(fa[x]);
}
int main() {
    ios :: sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n >> m;
    for (int i = 1; i <= n; i++) 
        fa[i] = i;
    for (int i = 1, x, y; i <= m; i++) {
        cin >> x >> y;
        f[x].ins(y);
        g[y].ins(x);
    }
    for (int i = 1; i <= n; i++)
        if (f[i].size() == 0) q.push(i);
    while (!q.empty()) {
        int v = q.front();
        q.pop();
        fa[v] = 0;
        for (auto u : g[v]) {
            f[u].era(v);
            if (f[u].size() == 0) q.push(u);
        }
    }
    for (int i = 1; i <= n; i++)
        if (f[i].size() == 1) q.push(i);
    while (!q.empty()) {
        int x = q.front();
        q.pop();
        int y = 0;
        for (auto v : f[x]) y = v;
        x = gf(x);
        y = gf(y);
        if (x == y) continue;
        if (g[x].size() > g[y].size())
            swap(x, y);
        for (auto z : g[x]) {
            f[z].era(x);
            f[z].ins(y);
            g[y].ins(z);
            if (f[z].size() == 1) q.push(z);
        }
        fa[x] = y;
    }
    cin >> k;
    while (k--) {
        int x, y; cin >> x >> y;
        x = gf(x);
        y = gf(y);
        if (x == 0 || y == 0 || x == y) {
            cout << "B";
        } else {
            cout << "H";
        }
    }
    return 0;   
}