公式

E - グループ分けとウイルス感染 / Group Division and Virus Infection 解説 by admin

Claude 4.6 Opus (Thinking)

Overview

This is a set partition optimization problem where \(N\) computers are divided into groups, and damage occurs only when all targets of a virus belong to the same group. The goal is to minimize the number of computers that suffer damage.

Analysis

Key Observations

  1. The constraint \(N \leq 15\): Computer sets can be represented as bitmasks, and enumerating subsets is feasible within practical time complexity.

  2. Damage for each group can be computed independently: For a given group \(G\), the damage within \(G\) is “the union of \(S_j\) for all viruses \(j\) whose infection targets \(S_j\) are subsets of \(G\).”

  3. Reduction to a set partition problem: We need to partition the entire set of computers into disjoint groups and minimize the total damage across all groups.

Issues with the Naive Approach

If we scan all viruses to compute the damage for each group \(G\), we need \(O(2^N \cdot M)\) preprocessing. While this would still work for \(N=15\), using SOS DP allows for a more efficient and elegant computation.

Algorithm

Step 1: Computing Damage Cost for Each Group (SOS DP)

Represent each virus’s infection targets \(S_j\) as a bitmask.

First, initialize virus_or[mask] as “the OR of \(S_j\) for all viruses whose \(S_j\) is exactly equal to mask” (effectively virus_or[mask] = mask if such a virus exists).

Next, perform SOS (Sum over Subsets) DP:

\[\text{damage}[G] = \bigcup_{\text{T} \subseteq G} \text{virus\_or}[T]\]

This can be computed with an \(O(N \cdot 2^N)\) DP by updating damage[G] |= damage[G ^ (1 << i)] (when bit \(i\) of \(G\) is set) for each bit \(i\).

damage[G] represents “the bitmask of computers that suffer damage when \(G\) forms a single group.” The cost is cost[G] = popcount(damage[G]).

Step 2: Set Partition DP

Let \(\text{dp}[\text{mask}]\) be “the minimum number of damaged computers when the computer set represented by bitmask mask is optimally divided into groups.”

The transition is:

\[\text{dp}[\text{mask}] = \min_{G \subseteq \text{mask}, G \neq \emptyset} \left( \text{cost}[G] + \text{dp}[\text{mask} \setminus G] \right)\]

We enumerate all non-empty subsets \(G\) of mask, and take the minimum of the sum of the cost of making \(G\) a single group and the optimal solution for the remainder.

The answer is \(\text{dp}[(1 \ll N) - 1]\).

Concrete Example

For \(N=3\), with virus infection targets \(\{1,2\}\) and \(\{2,3\}\): - Putting everyone in one group: both viruses activate, damage is \(\{1,2,3\}\) — 3 computers - Splitting into \(\{1,2\}\) and \(\{3\}\): the first virus activates (2 damaged), the second virus is blocked - Splitting into \(\{1\}\) and \(\{2,3\}\): the second virus activates (2 damaged), the first virus is blocked - Splitting into \(\{1\}\), \(\{2\}\), \(\{3\}\): both viruses are blocked, 0 damaged → optimal

Complexity

  • Time complexity: \(O(N \cdot 2^N + 3^N)\)
    • SOS DP: \(O(N \cdot 2^N)\)
    • Set partition DP (enumeration of all subsets): \(O(3^N)\) (approximately \(1.4 \times 10^7\) for \(N=15\))
  • Space complexity: \(O(2^N)\)

Implementation Notes

  • Set representation using bitmasks: Computer \(i\) corresponds to bit \((i-1)\), and set operations are performed efficiently using bitwise operations.

  • Subset enumeration technique: The idiom sub = (sub - 1) & mask efficiently enumerates all non-empty subsets of mask.

  • Correctness of SOS DP: damage[G] computes the OR of all virus masks that are subsets of \(G\), correctly obtaining the union of infection targets for all viruses where \(S_j \subseteq G\).

  • damage[G] is always a subset of G: Since only viruses with \(S_j \subseteq G\) are included, damage[G] & G == damage[G] always holds, so there is no need to worry about damage extending outside the group.

    Source Code

import sys

def solve():
    input_data = sys.stdin.buffer.read().split()
    idx = 0
    N = int(input_data[idx]); idx += 1
    M = int(input_data[idx]); idx += 1
    
    viruses = []
    for j in range(M):
        k = int(input_data[idx]); idx += 1
        mask = 0
        for i in range(k):
            s = int(input_data[idx]); idx += 1
            mask |= (1 << (s - 1))
        viruses.append(mask)
    
    # For each subset of computers (as bitmask), compute the set of computers
    # that get damaged if that subset forms a single group.
    # A virus j damages its computers if S_j is a subset of the group.
    # We need: for a group G (bitmask), damaged(G) = union of S_j for all j where S_j is subset of G.
    
    # Precompute: for each subset S, the damage (union of all virus masks that are subsets of S)
    # We can use SOS (sum over subsets) style DP but we need "union of virus masks for all submasks"
    
    # First, for each mask, store the OR of all virus S_j that equal that mask
    # Then do a "superset sum" to get for each group G, the OR of all virus masks that are subsets of G.
    
    # virus_at[mask] = OR of all S_j where S_j == mask
    # Actually we want: damage[G] = OR of S_j for all j where S_j is subset of G
    # This is the "subset sum" in the OR semiring over supersets.
    
    # Standard approach: damage[G] = OR over all T subset of G of virus_at[T]
    # We can compute this with SOS DP.
    
    full = (1 << N) - 1
    
    # virus_or[mask] = OR of all S_j that exactly equal mask
    virus_or = [0] * (1 << N)
    for mask in viruses:
        virus_or[mask] |= mask
    
    # SOS DP: damage[G] = OR of virus_or[T] for all T subset of G
    # Standard subset sum DP (over OR):
    damage = virus_or[:]
    for i in range(N):
        for g in range(1 << N):
            if g & (1 << i):
                damage[g] |= damage[g ^ (1 << i)]
    
    # Now damage[G] = bitmask of computers damaged if G is a single group
    # But we only care about damage within G, so effective damage = damage[G] & G
    # Actually, S_j subset of G means all bits of S_j are in G, so damage[G] is already subset of G.
    # (since virus_or[mask] = mask for non-empty, and OR of subsets of G is subset of G)
    
    # popcount
    pc = [0] * (1 << N)
    for i in range(1, 1 << N):
        pc[i] = pc[i >> 1] + (i & 1)
    
    # cost[G] = number of damaged computers if G forms one group = popcount(damage[G])
    cost = [0] * (1 << N)
    for g in range(1 << N):
        cost[g] = pc[damage[g]]
    
    # Now we need to partition {0,...,N-1} into groups to minimize total damage.
    # This is a set partition problem. We use DP over subsets.
    # dp[mask] = minimum total damage for partitioning the set 'mask' into groups
    
    # dp[0] = 0
    # dp[mask] = min over all non-empty subsets G of mask: cost[G] + dp[mask ^ G]
    
    # Enumerating all subsets of all masks: O(3^N) which for N=15 is ~14M, feasible.
    
    INF = float('inf')
    dp = [INF] * (1 << N)
    dp[0] = 0
    
    for mask in range(1, 1 << N):
        # Enumerate all non-empty subsets of mask
        sub = mask
        while sub:
            val = cost[sub] + dp[mask ^ sub]
            if val < dp[mask]:
                dp[mask] = val
            sub = (sub - 1) & mask
        # sub=0 is not a valid group (must be non-empty), so we stop
    
    print(dp[full])

solve()

This editorial was generated by claude4.6opus-thinking.

投稿日時:
最終更新: