ABC 213 G の類題に挙がっていたのでやってみた!
問題概要
頂点数 、辺数 の単純無向グラフが与えられます。また 以上 以下の整数 が与えられます。
グラフの各辺は % の確率でそれぞれ独立に消失します。グラフ全体が連結である確率を求めてください。
制約
考えたこと
なので、辺の消失パターン ( 通りある) すべてを調べる方法では間に合わない。
しかしながら、このようにグラフ上の指数探索系の問題では、「ある頂点 を一つ固定して、その周りの様子で場合分けする」のが定石だと思われる。ここでは、頂点 を含む連結成分 がどのようになるかで場合分けする考え方で問題を解いていこう。ここで、
= もとのグラフのうち、頂点集合を のみに限ったグラフを考えたとき、それが連結になる確率
とする。これを求める漸化式を立てていく。なお答えは と表される。
を求める漸化式
をいきなり求めることが難しいので、余事象を考えて全体から引くことを考えよう。つまり、 であるような をすべて考えて引いていく方針で考えてみよう。
を求めるために、 となる頂点 を選び、 内部で を含む連結成分 で場合分けして考えることにすると、
となる。ここで は、頂点集合 と頂点集合 との間にある辺集合を表します。
これは「部分集合の部分集合」を考えていくような遷移になっているので、 な bit DP となります。なお、 については、頂点集合 に含まれる辺の本数を として、
と求められます。 については で計算できます。
コード
#include <bits/stdc++.h> using namespace std; int main() { // 入力 int N, M; double P; cin >> N >> M >> P; P /= 100; vector<vector<bool>> G(N, vector<bool>(N, false)); for (int i = 0; i < M; ++i) { int a, b; cin >> a >> b; --a, --b; G[a][b] = G[b][a] = true; } // P の冪乗を確保しておく vector<double> power(M + 1, 1); for (int i = 0; i < M; ++i) power[i+1] = power[i] * P; // e(S) を求める vector<long long> e(1<<N, 0); for (int u = 0; u < N; ++u) { for (int v = 0; v < N; ++v) { if (G[u][v]) { int S = (1<<u) | (1<<v); e[S] = 1; } } } for (int i = 0; i < N; ++i) { for (int S = 0; S < (1<<N); ++S) { if (S & (1<<i)) { e[S] += e[S ^ (1<<i)]; } } } // O(3^N) DP vector<double> f(1<<N, 1.0); for (int S = 1; S < (1<<N); ++S) { // S に含まれる頂点 v を 1 つ選ぶ int v = -1; for (v = 0; v < N; ++v) if (S & (1<<v)) break; // S の部分集合を走査 for (int T = S - 1; T >= 0; --T) { T &= S; if (T & (1 << v)) { int c = e[S] - e[T] - e[S - T]; f[S] -= f[T] * power[c]; } } } cout << fixed << setprecision(10) << f[(1<<N)-1] << endl; }