CPSCO2019 Session2 G - MSTX

G - MSTX

問題

$n$ 頂点 $m$ 辺の重み付き無向連結グラフが与えられる. $i$ 番目の辺は $u_i$ と $v_i$ を結び,その重みは $w_i$ である. ここで $w_i$ は定数か,変数 $x$ である.

$q$ 個のクエリが与えられる. $j$ 番目のクエリは整数 $a_j$ からなる. このとき, $x = a_j$ としたときの最小全域木の重みを求めよ.

制約

  • $1 \leq n \leq 5 \times 10^4$
  • $n - 1 \leq m \leq 5 \times 10^4$
  • $w_i$ が定数のとき, $1 \leq w_i \leq 10^9$
  • $1 \leq q \leq 5 \times 10^4$
  • $1 \leq a_j \leq 10^9$

考察

以降 $w_i$ は昇順に並んでいるものとする( $x$ は末尾). また重みが定数の辺は $l$ 本あるとする.

Kruskal 法を軸に考える. まず, $x$ の辺なしで Kruskal 法を行い, $i$ 本使った時点での連結成分数は $g_i$ ,重みは $c_i$ だったとする. 次に $x$ の辺を全部使ってから Kruskal 法を行い, $x$ でない辺を $i$ 本使った時点での連結成分数は $xg_i$ ,重みは $xc_i$ だったとする.

ここで $w_{i - 1} \leq a_j \leq w_i$ だったとする(0-indexed). すると $x$ の辺は $i$ 本を追加した後で全部追加されることになる. つまり答えは

  • $i$ 本目までを追加
  • $x$ の辺を一気に追加
  • 残りの辺を全部追加

としたときの Kruskal 法の解となる.

1 番目は普通に $c_i$ となる.

ここで重要な事実として,「 $i$ 本目まで追加してから $x$ の辺を追加」と「 $x$ の辺を追加してから $i$ 本目まで追加」でどちらも同じ森が得られる. よって 3 番目は $xc_l - xc_i$ で求まる.

残るは 2 番目だが,これは $x$ の辺が何本使われたかが分かれば良い. 辺を 1 つ追加すると連結成分数が 1 減ることから,これは $g_i - xg_i$ で求まる. 「森の辺数 = 頂点数 - 連結成分数」と考えても良い.

$i$ の値は二分探索で求まるので,これで $\Theta((n + q) \log n)$ でこの問題が解けた.

実装例

提出 #8234140 - CPSCO2019 Session2

 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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include <iostream>
#include <algorithm>
#include <numeric>
#include <vector>

template <class Cost = int>
struct Edge { ... };

struct UnionFind { ... };

using lint = long long;

int main() {
    int n, m;
    std::cin >> n >> m;

    std::vector<Edge<lint>> edges, xedges;
    // 重みが定数の辺とxの辺を分離
    for (int i = 0; i < m; ++i) {
        int u, v, c;
        std::cin >> u >> v >> c;
        --u, --v;
        if (c == 0) {
            lint w;
            std::cin >> w;
            edges.emplace_back(u, v, w);
        } else {
            char w;
            std::cin >> w;
            xedges.emplace_back(u, v);
        }
    }

    std::sort(edges.begin(), edges.end());
    int l = edges.size();

    UnionFind uf(n), xuf(n);
    std::vector<lint> gnum(l + 1), cost(l + 1);
    gnum[0] = n, cost[0] = 0;
    // edgesのi本目まででKruskal

    std::vector<lint> xgnum(l + 1), xcost(l + 1);
    xgnum[0] = n, xcost[0] = 0;
    // xedgesを最初に使った状態で,edgesのi本目まででKruskal

    for (auto e : xedges) {
        if (xuf.same(e.src, e.dst)) continue;
        --xgnum[0];
        xuf.unite(e.src, e.dst);
    }

    for (int i = 1; i <= l; ++i) {
        gnum[i] = gnum[i - 1];
        cost[i] = cost[i - 1];
        xgnum[i] = xgnum[i - 1];
        xcost[i] = xcost[i - 1];

        auto e = edges[i - 1];
        if (!uf.same(e.src, e.dst)) {
            --gnum[i];
            cost[i] += e.cost;
            uf.unite(e.src, e.dst);
        }
        if (!xuf.same(e.src, e.dst)) {
            --xgnum[i];
            xcost[i] += e.cost;
            xuf.unite(e.src, e.dst);
        }
    }

    int q;
    std::cin >> q;
    for (int p = 0; p < q; ++p) {
        lint a;
        std::cin >> a;

        int i = std::lower_bound(edges.begin(), edges.end(),
                                 Edge<lint>(0, 0, a)) -
                edges.begin();
        // edgesをi本使った直後にxedgesが入る

        lint ans = cost[i] +
                   (gnum[i] - xgnum[i]) * a +
                   (xcost[l] - xcost[i]);
        // 減少した連結成分数 = xedgesから追加された辺数

        std::cout << ans << std::endl;
    }
    return 0;
}
Built with Hugo
テーマ StackJimmy によって設計されています。