公式

D - ウイルス検査と感染端末 / Virus Testing and Infected Terminals 解説 by admin

Claude 4.6 Opus (Thinking)

Overview

Given \(N\) terminals (\(N \leq 16\)), find a combination of infected terminals that is consistent with all \(M\) security scan results, minimizing the number of infected terminals. This is essentially a minimum Set Cover problem solved by exhaustive search using bitmasks.

Analysis

Problem Formulation

Representing each terminal as “infected (1)” or “not infected (0)”, the set of infected terminals is a subset of \(\{1, 2, \ldots, N\}\). Consistency with scan results translates into two types of constraints:

  • \(R_j = 0\) (no infection): All terminals in the scan’s target set are not infected. That is, these terminals are excluded from infection candidates.
  • \(R_j = 1\) (infection detected): At least one terminal in the scan’s target set is infected.

Key Observations

  1. \(R_j = 0\) constraints are easy to handle: Terminals included in sets judged as “no infection” are definitely not infected. These can be collectively excluded as “forbidden terminals”.

  2. \(R_j = 1\) constraints form a Hitting Set problem: Among the remaining candidate terminals, find a minimum subset that shares at least one common element with every “infection detected” scan’s target set. This is equivalent to the Set Cover problem and is NP-hard, but since \(N \leq 16\), exhaustive search is feasible.

Naive Approach

Trying all \(2^N\) subsets gives at most \(2^{16} = 65536\) subsets when \(N = 16\). Checking \(M\) constraints for each subset yields approximately \(65536 \times 100 \approx 6.5 \times 10^6\) operations, which is fast enough.

Algorithm

  1. Bitmask representation: Map terminal \(i\) to bit \(i-1\), and convert each scan’s target terminal set into a bitmask.

  2. Compute forbidden terminals: For all scans with \(R_j = 0\), aggregate the target terminal bits using OR to create a forbidden mask. The infection candidates (allowed) are the complement of this mask.

  3. Collect \(R_j = 1\) constraints: For each “infection detected” scan, AND its target mask with allowed and store the result in a positive_masks list.

  4. Exhaustive enumeration: Enumerate all subsets of allowed. For each subset \(s\), check whether \((s \mathbin{\&} pm) \neq 0\) holds for all positive_masks. If so, record the bit count (popcount) and update the minimum.

    • Use the technique s = (s - 1) & allowed to efficiently iterate only over subsets of allowed.
  5. Output the minimum value.

Concrete Example

For \(N=4\), with scan results “terminals {1,2} have infection” and “terminals {3,4} have no infection”: - forbidden = {3,4}allowed = {1,2} - positive_masks = [{1,2}] - Subsets of allowed: \(\{1,2\}, \{1\}, \{2\}, \emptyset\) - \(\{1\}\) satisfies the constraint, and \(|\{1\}| = 1\) → the answer is 1

Complexity

  • Time complexity: \(O(2^N \times M)\) (the number of subsets of allowed is at most \(2^N\), and \(M\) constraints are checked for each subset)
  • Space complexity: \(O(M)\) (for storing scan information)

Since \(N \leq 16, M \leq 100\), we get \(2^{16} \times 100 \approx 6.5 \times 10^6\), which is fast enough.

Implementation Notes

  • Set operations via bitmasks: By representing terminal sets as bitmasks, operations like “do they share a common element?” and “exclusion” can be performed in \(O(1)\) using just AND/OR operations.

  • Subset enumeration technique: s = (s - 1) & allowed enumerates subsets of allowed in descending order. To include \(0\) without omission, check s == 0 at the end of the loop to terminate.

  • Preprocessing \(R_j = 0\): By excluding forbidden terminals in advance, the search space is significantly reduced (e.g., if half the terminals are forbidden, it shrinks to \(2^{N/2}\)).

    Source Code

import sys

def solve():
    input_data = sys.stdin.read().split()
    idx = 0
    N = int(input_data[idx]); idx += 1
    M = int(input_data[idx]); idx += 1
    
    scans = []
    for j in range(M):
        K = int(input_data[idx]); idx += 1
        mask = 0
        for k in range(K):
            s = int(input_data[idx]); idx += 1
            mask |= (1 << (s - 1))
        R = int(input_data[idx]); idx += 1
        scans.append((mask, R))
    
    # We need to find minimum popcount subset of {0..N-1} (as bitmask) such that:
    # For each scan (mask, R):
    #   if R == 1: (subset & mask) != 0  (at least one infected in the scan set)
    #   if R == 0: (subset & mask) == 0  (no infected in the scan set)
    
    # First, for R==0 scans, no terminal in that scan set can be infected.
    # So we can compute a "forbidden" mask of terminals that must NOT be infected.
    forbidden = 0
    for mask, R in scans:
        if R == 0:
            forbidden |= mask
    
    # Allowed terminals (candidates for infection)
    allowed = ((1 << N) - 1) & ~forbidden
    
    # For R==1 scans, we need (subset & mask) != 0
    # Since subset is a subset of allowed, the effective constraint is (subset & (mask & allowed)) != 0
    # Collect the R==1 constraints
    positive_masks = []
    for mask, R in scans:
        if R == 1:
            effective = mask & allowed
            # effective must be non-zero (guaranteed by problem: a valid assignment exists)
            positive_masks.append(effective)
    
    # Now we need to find minimum size subset of 'allowed' bits that hits all positive_masks
    # This is a set cover problem. N <= 20, number of allowed bits <= 20.
    # Number of positive constraints <= 100.
    
    # We can use BFS over bitmasks ordered by popcount, or iterate by popcount.
    # But 2^20 = ~1M, and for each we check up to 100 constraints. That's ~100M which might be tight.
    # Let's optimize: precompute and check.
    
    # Actually, let's just enumerate subsets of 'allowed' in order of popcount.
    # We can do BFS by popcount or just iterate all subsets.
    
    # With N=20, 2^20 = 1048576. For each subset that is a subset of 'allowed',
    # we check all positive_masks. Let's do it.
    
    # To enumerate only subsets of 'allowed', we can use the subset enumeration trick.
    # Number of subsets of 'allowed' = 2^(popcount(allowed)) which is at most 2^20.
    
    # We want minimum popcount. Let's iterate all subsets of allowed and find the one
    # with minimum popcount that satisfies all constraints.
    
    # Optimization: iterate by popcount (0, 1, 2, ...) and return as soon as we find a valid one.
    
    if not positive_masks:
        print(0)
        return
    
    # Enumerate subsets of 'allowed' by increasing popcount
    # Use the submask enumeration: s = allowed, then (s-1) & allowed, etc.
    # But this doesn't go in popcount order.
    
    # Alternative: just check all 2^20 subsets. With bit operations this should be fast enough.
    
    best = N
    
    # Iterate all subsets of 'allowed'
    # Submask enumeration
    s = allowed
    while True:
        # Check if s satisfies all positive constraints
        valid = True
        for pm in positive_masks:
            if (s & pm) == 0:
                valid = False
                break
        if valid:
            pc = bin(s).count('1')
            if pc < best:
                best = pc
                if best == 0:
                    break
        if s == 0:
            break
        s = (s - 1) & allowed
    
    print(best)

solve()

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

投稿日時:
最終更新: