AtCoder Beginner Contest 152 F - Tree and Constraints

F - Tree and Constraints

問題

$n$ 頂点の木が与えられる.この木の各辺を白か黒に塗る方法で,以下の $m$ 個の条件を全て満たすものの数を求めよ.

  • $i$ 番目の条件は頂点 $u_i, v_i$ からなる.
  • $u_i$ と $v_i$ を結ぶパス上に,少なくとも 1 つ黒い辺がなければならない.

制約

  • $2 \leq n \leq 50$
  • $1 \leq m \leq 20$

考察

条件 $i$ を満た さない 辺の塗り方の集合を $S_i$ とすると,答えは $2^{n - 1} - |\cup S_i|$ となる.というわけで包除原理を使うと,答えは

$$ 2^{n - 1} + \sum_{T \subseteq [n]} (-1)^{|T|} \left| \bigcap_{i \in T} S_i \right| $$

となる.

$\left| \bigcap_{i \in T} S_i \right|$ を求めたい. ある塗り方が「 $S_i$ に属すること」と「 $u_i$ と $v_i$ を結ぶパス上の辺が全て白で塗られていること」が同値なので, $E_{u, v}$ を「パス $u, v$ 上に含まれる辺の集合」とすると,

$$ \left| \bigcap_{i \in T} S_i \right| = 2^{(n - 1) - \left| \bigcup_{i \in T} E_{u, v} \right|} $$

となる(白で塗られることが確定した辺以外は自由に塗っていい).これで $E_{u, v}$ を前計算することで解が求められる.

$E_{u, v}$ は DFS 等でそのまま求めてもよいが,経路復元等が必要で面倒.一方で $E_v$ を「 $v$ から根までのパス上の辺の集合」とすると,

$$ E_{u, v} = E_u \oplus E_v $$

となるので,辺集合を bit で管理するようにすれば,実装が楽だし計算量も落ちる(全点対調べる必要がないため).

実装例

提出 #9616745 - AtCoder Beginner Contest 152

 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
#include <iostream>
#include <vector>
#include <queue>

template <class Cost = int>
struct Edge { ... };
template <class Cost = int>
using Graph = std::vector<std::vector<Edge<Cost>>>;

using lint = long long;

void solve() {
    int n;
    std::cin >> n;

    Graph<> graph(n);
    for (int i = 0; i < n - 1; ++i) {
        int u, v;
        std::cin >> u >> v;
        --u, --v;
        graph[u].emplace_back(u, v);
        graph[v].emplace_back(v, u);
    }

    // BFSでn-1を根とする根付き木に変換
    // iから親に伸びる辺の番号をiとする
    std::vector<int> par(n, -1);
    std::vector<lint> paths(n, 0);

    std::queue<int> que;
    que.push(n - 1);
    par[n - 1] = n - 1;

    while (!que.empty()) {
        int v = que.front();
        que.pop();

        for (auto e : graph[v]) {
            int u = e.dst;
            if (par[u] >= 0) continue;
            par[u] = v;
            paths[u] = (paths[v] | (1LL << u));
            que.push(u);
        }
    }

    int m;
    std::cin >> m;

    std::vector<lint> ss(m);
    // ui-vi間の辺の集合
    for (auto& s : ss) {
        int u, v;
        std::cin >> u >> v;
        --u, --v;
        s = (paths[u] ^ paths[v]);
    }

    // 包除原理
    lint ans = 0;
    for (int b = 0; b < (1 << m); ++b) {
        int sign = (__builtin_popcount(b) % 2 == 0 ? 1 : -1);

        lint s = 0;  // 白で塗る辺の集合
        for (int i = 0; i < m; ++i) {
            if ((b >> i) & 1) s |= ss[i];
        }

        int free = n - 1 - __builtin_popcountll(s);
        ans += sign * (1LL << free);
    }

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