第 6 回 ドワンゴからの挑戦状 本選 B - Harvest Festival

B - Harvest Festival

問題

nn 頂点 mm 辺の単純無向重み付きグラフが与えられる.辺 ii の重みは did_i . さらに,頂点集合の部分集合 WVW \subseteq V が与えられる.

SVS \subseteq V に対して, f(S)f(S) を「 WW のうち, SS の任意の頂点からの距離が ll 以内である頂点の集合」と定める.より形式的には,以下のように定める.

f(S)={wWvS,  d(v,w)l} f(S) = \{ w \in W \mid \forall v \in S, \; d(v, w) \leq l \}

TWT \subseteq W について, f(S)=Tf(S) = T なる SV\emptyset \neq S \subseteq V の個数を求めよ1

制約

  • 1n1051 \leq n \leq 10^5
  • 0m2×1050 \leq m \leq 2 \times 10^5
  • 1W(=k)201 \leq |W| (= k) \leq 20
  • 1l,di1091 \leq l, d_i \leq 10^9

考察

まず各 wWw \in W について,そこから距離 ll 以内の頂点集合を UwU_w とする.これは Dijkstra 法により O(k(n+m)logm)O(k (n + m) \log m) で求まる.

これを拡張して, TWT \subseteq W について, UT=wTUwU_T = \bigcap_{w \in T} U_w と定める. つまり UTU_T は「 TT の任意の頂点から距離 ll 以内の頂点集合」である.

すると,任意の SUTS \subseteq U_T について f(S)Tf(S) \supseteq T が成り立つ.しかしこれは「ぴったり」,つまり f(S)=Tf(S) = T になっていない. それでもこれは メビウス変換 によって「ぴったり」にできる.

具体的には, f(S)Tf(S) \supseteq T を満たすものから f(S)Tf(S) \supsetneq T を満たすものを抜けばいい. 言い換えると, UTU_T から各 TTT' \supsetneq T について UTU_{T'} を抜けばいい. よって UT|U_T| さえ求まっていれば, 高速メビウス変換 によって O(k2k)O(k 2^k) で解が求まる.

UT|U_T| を求めるアルゴリズムとして,「各 vVv \in V について Xv={wWd(v,w)l}X_v = \{ w \subseteq W \mid d(v, w) \leq l \} を求め,各 TXvT \subseteq X_v に対して UT|U_T| を増やす」という方法を考える. そのままでは O(n2k)O(n 2^k) で間に合わないのだが,

  • vVv \in V について UXv|U_{X_v}| を 1 増やす.
  • これに対して(上位集合の) 高速ゼータ変換 を行う.

とすることで O(nk+k2k)O(nk + k 2^k) に落ちて間に合う.

実装例

提出 #9964494 - 第6回 ドワンゴからの挑戦状 本選

 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
91
92
93
94
95
96
97
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <limits>
#include <tuple>

using lint = long long;

// modint
template <int MOD>
struct ModInt { ... };

constexpr int MOD = 998244353;
using mint = ModInt<MOD>;


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

template <class Cost = int>
using Graph = std::vector<std::vector<Edge<Cost>>>;

// dijkstra
template <class T>
using MaxHeap = std::priority_queue<T>;
template <class T>
using MinHeap = std::priority_queue<T, std::vector<T>, std::greater<T>>;

constexpr lint INF = std::numeric_limits<lint>::max();

template <class Cost>
std::vector<Cost> dijkstra(const Graph<Cost>& graph, int s) { ... }

// main
void solve() {
    int n, m, k;
    lint l;
    std::cin >> n >> m >> k >> l;

    std::vector<int> ws(k);
    for (auto& w : ws) std::cin >> w;

    Graph<lint> graph(n);
    while (m--) {
        int u, v;
        lint d;
        std::cin >> u >> v >> d;
        graph[u].emplace_back(u, v, d);
        graph[v].emplace_back(v, u, d);
    }

    std::vector<std::vector<lint>> dist(k);
    for (int i = 0; i < k; ++i) dist[i] = dijkstra(graph, ws[i]);

    std::vector<int> cnt(1 << k, 0);
    // cnt[T] = T中の全ての街から距離l以内の街の数
    for (int v = 0; v < n; ++v) {
        int b = 0;
        for (int i = 0; i < k; ++i) {
            if (dist[i][v] <= l) b |= (1 << i);
        }
        ++cnt[b];
    }

    // 上位集合に対する高速ゼータ変換
    for (int i = 0; i < k; ++i) {
        for (int b = (1 << k) - 1; b >= 0; --b) {
            if ((b >> i) & 1) {
                cnt[b ^ (1 << i)] += cnt[b];
            }
        }
    }

    std::vector<mint> xs(1 << k);
    for (int b = 0; b < (1 << k); ++b) {
        // 選ばられるべきは,cnt[T]個の頂点集合の部分集合で空でないもの
        xs[b] = (mint(2).pow(cnt[b])).val - 1;
    }

    // 上位集合に対する高速メビウス変換
    for (int i = 0; i < k; ++i) {
        for (int b = 0; b < (1 << k) - 1; ++b) {
            if (((b >> i) & 1) == 0) {
                xs[b] -= xs[b | (1 << i)];
            }
        }
    }

    int ans = 0;
    for (int b = 0; b < (1 << k); ++b) {
        ans ^= xs[b].val;
    }

    std::cout << ans << std::endl;
}

  1. 実際は mod998,244,353\bmod 998,244,353 を取ったものの XOR を出力する. ↩︎

Built with Hugo
テーマ StackJimmy によって設計されています。