mayoko’s diary

プロコンとかいろいろ。

yukicoder No.298 話の伝達

はい・・・・・・・・・・・・・・・・・・・・・・・・・・・・・

解法

割と引っかかる人多い気がするので誤解法も紹介します。
想定誤解法では, dp[i] = (i に話が伝達しない確率)として, 1-dp[N-1] を求めることを目標にします。各 i の dp[i] を求めるためには, メモ化再帰を使います。

まず, i に話を直接伝達させる可能性のある頂点の集合 S を列挙します。で, その各頂点に対して dp[j] を求めます。その後, S の部分集合を全列挙して, 「部分集合に含まれる頂点には話が伝達している, それ以外には話が伝達していない, という条件で, i に話が伝達しない確率はいくつか」というのを求めます。これの合計が dp[i] になりそうですね(嘘です)。

この解法をコードにしたのがこちら。

#39763 No.298 話の伝達 - yukicoder

ですが, これは嘘解法です。なぜかを考えるために challenge01.txt の入力を見てみます。
4 4
0 1 50
1 2 50
1 3 50
2 3 50
f:id:mayokoex:20151107112138j:plain

これで 3 に話が伝達しない確率を考えたいですが, このようなグラフの場合だと, そもそも 1 に話が伝達していなければ 2 に話が伝達していることはありえないです。つまり, 2 に話が伝達するかどうかは 1 の頂点に話が伝達しているかどうかに依存しています。
よって, 先ほどのような場合分けをしてしまうと, 独立でない事象の足し算になってしまい, 正しい答えが得られない可能性があります。これが嘘解法である原因です。

ではどうすれば良いのかというと, 「伝達する頂点はここ」と決め打ちした後, そのようになる確率を求めれば良いです(もちろん頂点 0 と頂点 N-1 は伝達する頂点にします)。
伝達する頂点に実際に伝達する確率を求めるために, 余事象を考えます。頂点 i に話を直接伝達させる可能性のある頂点 j それぞれについて,
・j に話が伝達することになっているなら, j -> i の辺を経由してはいけない。その確率は 1-c
・j に話が伝達しないことになっているなら, j -> i の辺は経由しようがしまいがどうでも良い。その確率は 1
これらの値を掛けあわせてもので 1 を引くと, i に話が伝達する確率が得られます。

このような考え方をすると, 先ほどのような依存関係を回避でき, それぞれの場合を独立事象と考えることができるので, 正しい答えを求めることが出来ます。

struct Edge {
    int from;
    double p;
    Edge(){}
    Edge(int f, double p) : from(f), p(p) {}
};
const int MAXM = 300;
int A[MAXM], B[MAXM], C[MAXM];
vector<Edge> es[22];
bool is[22];

int main() {
    int N, M;
    cin >> N >> M;
    for (int i = 0; i < M; i++) {
        cin >> A[i] >> B[i] >> C[i];
        es[B[i]].emplace_back(A[i], C[i]/100.);
    }
    if (N == 1) {
        cout << 0 << endl;
        return 0;
    }
    double ans = 0;
    for (int s = 0; s < 1<<(N-2); s++) {
        memset(is, false, sizeof(is));
        is[0] = is[N-1] = true;
        for (int i = 1; i < N-1; i++) if ((s>>(i-1))&1) is[i] = true;
        vector<double> p(N, 1);
        p[0] = 0;
        for (int i = 0; i < N; i++) {
            for (Edge e : es[i]) if (is[e.from]) {
                p[i] *= 1-e.p;
            }
        }
        double prob = 1;
        for (int i = 0; i < N; i++) {
            if (is[i]) prob *= 1-p[i];
            else prob *= p[i];
        }
        ans += prob;
    }
    printf("%.15lf\n", ans);
    return 0;
}