# ABC149-F Surrounded Nodes
https://atcoder.jp/contests/abc149/tasks/abc149_f
問題概要: 問題を読む
主客転倒、各頂点$0, 1, \dots, N - 1, N$について、その頂点が穴あき度に寄与するような(その頂点が$S$に含まれて、かつ自身が白色となる)$S$の通り数($e_v$)を考えます。
期待値の線形性より、$\frac{\sum_{i = 1}^{N}e_v}{2^N}$が解になります。
$e_v$の計算方法について、余事象を考えます。$v$が黒色の時は自明なので、白色の時(だけど、$S$に含まれないので穴あき度に寄与しない)数を数えたいです。
$S$が空グラフである時は当然$v$は$S$に含まれません。そうで無い時、($v$を根と仮定して)「$v$のちょうど一つの部分木に黒頂点が含まれており、他の部分木には黒頂点が含まれていない」です。このような色の塗り方は、部分木のサイズが求まっていれば簡単なdpで数えることができます。
以上より、部分木のサイズを「現在頂点$v$を見ている時、$v$を根とした上で子頂点の部分木のサイズが求まっている」ようにdfsで更新しながら(全方位木dp?)以上のdpを回して解に足せば良いです。
サイズの更新は、予め適当な頂点$r$を根とした各頂点の部分木のサイズを求めておいて、
```cpp
dfs(int v, int p) {
int origin = size[v];
for (auto x : g[v]) if (x != v) {
size[v] = n - size[x];
dfs(x, v);
}
size[v] = origin;
}
```
とすればできます。お手軽。
### 実装
```cpp=
#include <bits/stdc++.h>
#include <atcoder/modint>
using mint = atcoder::modint1000000007;
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
int n; std::cin >> n;
std::vector g(n, std::vector<int>{});
for (int _{} ; _ < n - 1 ; _++) {
int u, v; std::cin >> u >> v;
u--; v--;
g[u].emplace_back(v);
g[v].emplace_back(u);
}
std::vector<int> sz(n);
auto rec{[&](auto rec, int v, int p) -> int {
sz[v] = 0;
for (auto x : g[v]) if (x != p) {
sz[v] += rec(rec, x, v);
}
sz[v]++;
return sz[v];
}};
rec(rec, 0, -1);
std::vector<mint> p2(n + 1, mint{1});
for (int i{} ; i < n ; i++) p2[i + 1] = p2[i] * mint{2};
mint ans{};
auto rec2{[&](auto rec, int v, int p) -> void {
mint val{p2[n - 1]};
std::vector<mint> dp(2);
dp[0] = mint{1};
for (auto x : g[v]) {
std::vector<mint> nxt(2);
nxt[0] = dp[0];
nxt[1] = dp[1] + dp[0] * (p2[sz[x]] - 1);
dp = std::move(nxt);
}
val -= dp[0] + dp[1];
ans += val;
int origin{sz[v]};
for (auto x : g[v]) if (x != p) {
sz[v] = n - sz[x];
rec(rec, x, v);
sz[v] = origin;
}
}};
rec2(rec2, 0, -1);
ans /= p2[n];
std::cout << ans.val() << std::endl;
}
```
## 感想
立式等わりとスムーズにできた。OK