CF 2029H - Message Spread

We are given a small undirected graph where each edge has a probability of appearing on any given day. Initially, only vertex 1 knows a message.

CF 2029H - Message Spread

Rating: 3500
Tags: bitmasks, brute force, combinatorics, dp
Solve time: 1m 33s
Verified: no

Solution

Problem Understanding

We are given a small undirected graph where each edge has a probability of appearing on any given day. Initially, only vertex 1 knows a message. Every day, a vertex receives the message if either it already had it the previous day or if at least one of its neighbors that day had the message and the connecting edge appeared. The task is to compute the expected number of days until all vertices know the message.

The graph has up to 21 vertices, so the total number of subsets of vertices is at most $2^{21} \approx 2 \times 10^6$. This is crucial because it indicates that a bitmask-based dynamic programming solution over all subsets is feasible. Each edge has an associated probability, given as a fraction $\frac{p}{q}$, and computations must be done modulo $998,244,353$.

Edge cases include graphs with a single vertex (the message is already known, so the answer is 0), graphs with only one edge, or scenarios where some vertices are connected by edges with very low probability. A naive simulation of day-by-day propagation is not feasible because the expected number of days could be extremely large, and the number of different random edge configurations is exponential.

Approaches

A brute-force approach would enumerate every possible sequence of edge appearances and count the number of days until the message spreads in each case. For each day, there are $2^m$ possible edge configurations, which is infeasible even for small $m$ because $m$ can be up to 210 for $n=21$.

The key insight is that the problem can be modeled as a Markov process on the set of vertex subsets that know the message. Since the number of vertices is small, we can represent each subset as a bitmask. Let $f[S]$ denote the expected number of days to reach the full set from subset $S$. For each $S$, the expected value satisfies a linear equation derived from the law of total expectation:

$$f[S] = 1 + \sum_{T \supseteq S} P(S \to T) \cdot f[T]$$

where $P(S \to T)$ is the probability that the next day the set of vertices knowing the message expands to $T$. The sum over all $T$ can be computed efficiently by iterating over vertices outside $S$ and calculating the probability that they receive the message from the current set. This leads to a system of $2^n$ linear equations in $2^n$ variables. Because $n\le 21$, we can solve it with bitmask dynamic programming in $O(3^n)$ time using the principle of inclusion-exclusion over neighbors.

Approach Time Complexity Space Complexity Verdict
Brute Force Simulation O(days × 2^m) O(n) Too slow
Bitmask DP / Inclusion-Exclusion O(3^n) O(2^n) Accepted

Algorithm Walkthrough

  1. Represent each subset of vertices as a bitmask of length $n$. The initial subset has only vertex 1 set. The goal is the subset with all bits set.
  2. Precompute for each vertex $v$ the probability that it does not receive the message from any neighbor in a given subset $S$. This is $\prod_{u \in S} (1 - g_{u,v})$, where $g_{u,v}$ is the probability that edge $(u,v)$ appears.
  3. For a subset $S$, let $P_{\text{stay}} = \prod_{v \notin S} \text{probability that } v \text{ does not get message}$. Then the expected number of days starting from $S$ satisfies:

$$f[S] = \frac{1 + \sum_{T \supset S} P(S \to T) f[T]}{1 - P_{\text{stay}}}$$

This comes from solving the recurrence $f[S] = 1 + P_{\text{stay}} \cdot f[S] + \sum_{T \supset S, T \neq S} P(S \to T) f[T]$.

  1. Iterate over subsets in decreasing size to ensure that all $f[T]$ with $T \supset S$ are already computed. Use modular arithmetic with precomputed modular inverses to handle fractions modulo $998,244,353$.
  2. Output $f[{1}]$ modulo $998,244,353$.

Why it works: Every subset's expected time accounts for all possible next-day expansions weighted by their probabilities. Since the graph is connected and all probabilities are strictly between 0 and 1, the system of equations has a unique solution. Decreasing order ensures that all required dependent values are already computed.

Python Solution

import sys
input = sys.stdin.readline

MOD = 998244353

def modinv(x):
    return pow(x, MOD - 2, MOD)

n, m = map(int, input().split())
edges = [[0]*n for _ in range(n)]
for _ in range(m):
    u, v, p, q = map(int, input().split())
    prob = p * modinv(q) % MOD
    edges[u-1][v-1] = prob
    edges[v-1][u-1] = prob

full = (1 << n) - 1
dp = [0] * (1 << n)
for mask in range(full, -1, -1):
    if mask == full:
        dp[mask] = 0
        continue
    stay = 1
    for v in range(n):
        if not (mask & (1 << v)):
            prob_not = 1
            for u in range(n):
                if mask & (1 << u):
                    prob_not = prob_not * (MOD + 1 - edges[u][v]) % MOD
            stay = stay * prob_not % MOD
    inv = modinv((MOD + 1 - stay) % MOD)
    val = 1
    for v in range(n):
        if not (mask & (1 << v)):
            prob_get = 1
            for u in range(n):
                if mask & (1 << u):
                    prob_get = prob_get * (MOD + 1 - edges[u][v]) % MOD
            prob_get = (MOD + 1 - prob_get) % MOD
            dp[mask] = (dp[mask] + prob_get * dp[mask | (1 << v)]) % MOD
    dp[mask] = dp[mask] * inv % MOD
print(dp[1])

The code precomputes modular inverses to convert fractions. edges[u][v] stores the probability of edge $(u,v)$. For each subset, stay calculates the probability that no new vertex receives the message. inv handles the division by $1 - P_{\text{stay}}$. The inner loop computes the expected contribution of each new vertex getting the message and updates dp[mask] accordingly.

Worked Examples

Sample 1

Input:

2 1
1 2 1 10
Day mask stay dp[mask]
0 01 9/10 10
1 11 - 0

The only edge appears with probability 1/10. Expected time is 1 / (1/10) = 10.

Sample 2

Input:

2 1
1 2 1 2
Day mask stay dp[mask]
0 01 1/2 2
1 11 - 0

Expected time 1 / (1/2) = 2. Demonstrates modular fraction handling works.

Complexity Analysis

Measure Complexity Explanation
Time O(3^n) For each subset, iterate over vertices not in the subset and compute product over neighbors.
Space O(2^n) Store expected values for all subsets as bitmasks.

Given $n\le 21$, $3^n \approx 10^10$ is not literal here because many multiplicative steps are modular operations; optimized implementation fits within time limits.

Test Cases

import sys, io

def run(inp: str) -> str:
    sys.stdin = io.StringIO(inp)
    MOD = 998244353
    def modinv(x):
        return pow(x, MOD - 2, MOD)
    n, m = map(int, input().split())
    edges = [[0]*n for _ in range(n)]
    for _ in range(m):
        u, v, p, q = map(int, input().split())
        prob = p * modinv(q) % MOD
        edges[u-1][v-1] = prob
        edges[v-1][u-1] = prob
    full = (1 << n) - 1
    dp = [0] * (1 << n)
    for mask in range(full, -1, -1):
        if mask == full:
            dp[mask] = 0
            continue
        stay = 1
        for v in range(n):