「题解」蓝书第四章 - 例题

上课看这个格外有韵味

0x41 并查集

P1955 [NOI2015] 程序自动分析

怎么写过。算了再写一遍。

考虑到 $i, j \le 10^9$,我们还要离散化一下,这么变态。

整体还是非常显然的,注意到相等关系具有传递性,用并查集维护即可。

Code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
using i3 = array<int, 3>;
void solve() {
    int n;
    cin >> n;
    vector<i3> vec(n);
    vector<int> d;
    for (auto &[i, j, e] : vec) {
        cin >> i >> j >> e;
        d.emplace_back(i);
        d.emplace_back(j);
    }
    sort(d.begin(), d.end());
    d.erase(unique(d.begin(), d.end()), d.end());
    DSU dsu(d.size() + 1);
    for (auto &[i, j, e] : vec) {
        i = lower_bound(d.begin(), d.end(), i) - d.begin();
        j = lower_bound(d.begin(), d.end(), j) - d.begin();
        if (e == 1) {
            dsu.Merge(i, j);
        }
    }
    for (auto &[i, j, e] : vec) {
        if (e)
            continue;
        if (dsu.Find(i) == dsu.Find(j)) {
            cout << "NO\n";
            return;
        }
    }
    cout << "YES\n";
}

UVa 不写,略。

P1196 [NOI2002] 银河英雄传说

我擦,废话怎么那么多。

考虑我们需要维护一个节点和其父节点的距离,那么显然这个是 $\tt{louisdlee}$ 最喜欢的带权并查集。

然后我们只要简单魔改一下这个并查集的模板就可以了。

Code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
struct DSU {
    std::vector<int> f, sz;
    std::vector<int> d;

    DSU(int n) : d(n), f(n), sz(n, 1) { std::iota(f.begin(), f.end(), 0); }

    int Find(int x) {
        if (f[x] == x)
            return x;
        int fa = Find(f[x]);
        d[x] += d[f[x]];
        return f[x] = fa;
    }

    void Merge(int x, int y) {
        x = Find(x), y = Find(y);
        if (x == y)
            return;
        f[y] = x, d[y] = sz[x];
        sz[x] += sz[y];
    }
};

using i3 = array<int, 3>;
void solve() {
    int m;
    cin >> m;
    vector<i3> vec(m);
    set<int> st;
    for (auto &[op, i, j] : vec) {
        char ch;
        cin >> ch >> i >> j;
        st.insert(i); st.insert(j);
        op = ch == 'C';
    }
    DSU dsu(st.size() + 1);
    for (auto &[op, i, j] : vec) {
        if (op == 0) {
            dsu.Merge(j, i);
        } else {
            if (dsu.Find(i) != dsu.Find(j)) {
                cout << "-1\n";
            } else {
                cout << abs(dsu.d[i] - dsu.d[j]) - 1 << "\n";
            }
        }
    }
}

P5937 [CEOI1999] Parity Game

感觉很变态啊。

考虑奇偶不难想到异或,然后我们思考一下前缀异或和 $\rm{sum}_{l-1} \oplus \rm{sum}_r$ 异或出来就是区间 $[l,r]$ 的奇偶性。然后我们用并查集维护每一个区间端点 $\rm{sum}_i$ 不难发现对于给定的 $l, r$ 和 $\rm{ans}$,在异或边权的意义上有传递性,然后直接用带权并查集维护即可。

考虑 $n \le 10^9$ 还要离散化一下。

我算是知道了为什么那么多人不封装的并查集了。

Code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
struct DSU {
    std::vector<int> f, sz;
    std::vector<int> d;

    DSU(int n) : d(n), f(n), sz(n, 1) { std::iota(f.begin(), f.end(), 0); }

    int Find(int x) {
        if (f[x] == x)
            return x;
        int fa = Find(f[x]);
        d[x] ^= d[f[x]];
        return f[x] = fa;
    }
};

using i3 = array<int, 3>;
void solve() {
    int n, m;
    cin >> n >> m;
    vector<i3> ask(m);
    vector<int> d;
    for (auto &[l, r, ans] : ask) {
        string op;
        cin >> l >> r >> op;
        l--;
        ans = op.size() % 2;
        d.emplace_back(l), d.emplace_back(r);
    }
    sort(d.begin(), d.end());
    d.erase(unique(d.begin(), d.end()), d.end());
    DSU dsu(d.size() + 1);
    for (int i = 0; auto &[l, r, ans] : ask) {
        l = lower_bound(d.begin(), d.end(), l) - d.begin();
        r = lower_bound(d.begin(), d.end(), r) - d.begin();
        int p = dsu.Find(l), q = dsu.Find(r);
        if (p == q) {
            if ((dsu.d[l] ^ dsu.d[r]) != ans) {
                cout << i << "\n";
                return;
            }
        } else {
            dsu.f[p] = q, dsu.d[p] = dsu.d[l] ^ dsu.d[r] ^ ans;
        }
        i++;
    }
    cout << m << "\n";
}

P2024 [NOI2001] 食物链

考虑开一个大小为 $3n$ 的并查集,其中 $[1,n]$ 维护同类信息, $[n+1, 2n]$ 维护捕食信息,然后 $[2n+1,3n]$ 维护天敌信息。

若 $X$ 吃 $Y$,则合并 $(X+n, Y)$,$(X,Y+2n)$ 和 $(X+2n, Y+n)$,若 $X$ 与 $Y$ 为同类,则合并 $(X, Y)$,$(X+n, Y+n)$ 和 $(X+2n, Y+2n)$。处理每一句话之前判断每句话的真假(即对于 $X$ 与 $Y$ 同类,$X+n \nsim Y$ 且 $X \nsim Y$;对于 $X$ 吃 $Y$,$X \nsim Y$ 且 $X \nsim Y+n$)即可。

需要注意还需要判断 $X>n \lor Y>n$ 的情况。

![CODE]-

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void solve() {
    int n, k;
    cin >> n >> k;
    DSU dsu(4 * n);
    int ans = 0;
    while (k--) {
        int op, x, y;
        cin >> op >> x >> y;
        if (x > n or y > n) {
            ans++;
            continue;
        }
        if (op == 1) {
            if (dsu.Same(x + n, y) or dsu.Same(x, y + n)) {
                ans++;
                continue;
            }
            dsu.Merge(x, y);
            dsu.Merge(x + n, y + n);
            dsu.Merge(x + 2 * n, y + 2 * n);
        } else {
            if (dsu.Same(x, y) or dsu.Same(x, y + n)) {
                ans++;
                continue;
            }
            dsu.Merge(x + n, y);
            dsu.Merge(x, y + 2 * n);
            dsu.Merge(x + 2 * n, y + n);
        }
    }
    cout << ans <<"\n";
}

先写这么多,吃饭了。

赣ICP备2024042422号
使用 Hugo 构建
主题 StackJimmy 设计