B - Harvest Festival
問題
n 頂点 m 辺の単純無向重み付きグラフが与えられる.辺 i の重みは di .
さらに,頂点集合の部分集合 W⊆V が与えられる.
S⊆V に対して, f(S) を「 W のうち, S の任意の頂点からの距離が l 以内である頂点の集合」と定める.より形式的には,以下のように定める.
f(S)={w∈W∣∀v∈S,d(v,w)≤l}
各 T⊆W について, f(S)=T なる ∅=S⊆V の個数を求めよ.
制約
- 1≤n≤105
- 0≤m≤2×105
- 1≤∣W∣(=k)≤20
- 1≤l,di≤109
考察
まず各 w∈W について,そこから距離 l 以内の頂点集合を Uw とする.これは Dijkstra 法により O(k(n+m)logm) で求まる.
これを拡張して, T⊆W について, UT=⋂w∈TUw と定める.
つまり UT は「 T の任意の頂点から距離 l 以内の頂点集合」である.
すると,任意の S⊆UT について f(S)⊇T が成り立つ.しかしこれは「ぴったり」,つまり f(S)=T になっていない.
それでもこれは メビウス変換 によって「ぴったり」にできる.
具体的には, f(S)⊇T を満たすものから f(S)⊋T を満たすものを抜けばいい.
言い換えると, UT から各 T′⊋T について UT′ を抜けばいい.
よって ∣UT∣ さえ求まっていれば, 高速メビウス変換 によって O(k2k) で解が求まる.
∣UT∣ を求めるアルゴリズムとして,「各 v∈V について Xv={w⊆W∣d(v,w)≤l} を求め,各 T⊆Xv に対して ∣UT∣ を増やす」という方法を考える.
そのままでは O(n2k) で間に合わないのだが,
- 各 v∈V について ∣UXv∣ を 1 増やす.
- これに対して(上位集合の) 高速ゼータ変換 を行う.
とすることで O(nk+k2k) に落ちて間に合う.
実装例
提出 #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;
}
|