公式

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

Gemini 3.0 Flash (Thinking)

Overview

Given \(N\) terminals and \(M\) scan results, the problem asks to find a “combination of infected terminals” that is consistent with all scan results while minimizing the number of infected terminals.

Approach

This problem can be solved by thinking through the following three steps.

1. Identify terminals that are definitely not infected

When a scan result is \(R_j = 0\) (no infection), all terminals included in that scanned set are confirmed to be “not infected”. First, check all scans with \(R_j = 0\), and mark any terminal that appeared at least once in a “no infection” set as a “clean terminal.”

2. Organize the “infection detected” scan conditions

Next, consider the cases where the scan result is \(R_j = 1\) (infection detected). This condition means “at least one terminal in that set is infected.” Here, the “clean terminals” identified in Step 1 cannot be the cause of “infection detected” even if they are included in any \(R_j = 1\) set. Therefore, for each \(R_j = 1\) set, we remove the clean terminals and create a new set containing only “potentially infected terminals.”

3. Satisfy all conditions with the minimum number of terminals (Set Cover Problem)

The remaining task is to “select at least one terminal from each \(R_j = 1\) set, minimizing the total number of selected terminals.” This takes the form of the well-known “Set Cover Problem,” but since the constraint is \(N \leq 16\), which is very small, it can be solved efficiently using exhaustive search with bitmasks or dynamic programming (DP).

Algorithm

  1. Excluding clean terminals: Enumerate the terminals included in scans with \(R_j = 0\), and identify the remaining \(K\) terminals (potentially infected terminals).
  2. Creating target sets: For each scan with \(R_j = 1\), manage the set of “potentially infected terminals” it contains using bitmasks or similar.
  3. Optimization via Bit DP: Explore all \(2^K\) combinations of which terminals to select among the \(K\) potentially infected terminals.
    • dp[mask]: Holds how many \(R_j = 1\) scan conditions are satisfied by the terminal selection mask (which terminals are assumed to be infected).
    • Among all mask values that satisfy every scan condition (all \(R_j = 1\) ones), find the one with the fewest set bits (infected terminals) as the answer.

*Note: In the reference solution, to optimize computation, a DP approach is used that updates “which scans are covered by a given set of terminals.”

Complexity

Let \(N\) be the number of terminals and \(M\) be the number of scans.

  • Time complexity: \(O(MN + 2^N)\)
    • \(O(MN)\) for reading input and identifying clean terminals.
    • \(O(2^N)\) for exploring combinations of potentially infected terminals.
    • Since \(N \leq 16\), we have \(2^{16} = 65,536\), which runs well within the time limit.
  • Space complexity: \(O(M + 2^N)\)
    • Depends on storing scan information and the DP table size.

Implementation Notes

  • Leveraging bit operations: By representing “which scan conditions are satisfied” and “which terminals are selected” as bits (\(0\) or \(1\)) of integers, set union (OR operation) and membership checks can be performed efficiently.

  • Reducing redundant conditions: If the target terminals of scan \(A\) are a subset of the target terminals of scan \(B\), then satisfying scan \(A\) automatically satisfies scan \(B\). By removing such redundant scan results in advance, constant-factor speedup is possible (the minimal_sets processing in the code).

    Source Code

import sys

def solve():
    # Read all input at once for faster processing
    input_data = sys.stdin.read().split()
    if not input_data:
        return
    
    # N: number of terminals, M: number of scans
    N = int(input_data[0])
    M = int(input_data[1])
    
    scans = []
    ptr = 2
    for _ in range(M):
        K_j = int(input_data[ptr])
        S_j = list(map(int, input_data[ptr+1 : ptr+1+K_j]))
        R_j = int(input_data[ptr+1+K_j])
        scans.append((S_j, R_j))
        ptr += 1 + K_j + 1
        
    # 1. Identify terminals that must be clean (R_j = 0 means no infected in that set)
    must_be_clean = [False] * (N + 1)
    for s_j, r_j in scans:
        if r_j == 0:
            for terminal in s_j:
                must_be_clean[terminal] = True
                
    # 2. Identify potential infected terminals (those not proven to be clean)
    possible_infected = []
    for i in range(1, N + 1):
        if not must_be_clean[i]:
            possible_infected.append(i)
            
    # 3. Identify scans that must have at least one infected terminal (R_j = 1)
    # For these scans, we only consider terminals that are not already proven clean.
    target_sets = []
    for s_j, r_j in scans:
        if r_j == 1:
            t_j = [t for t in s_j if not must_be_clean[t]]
            target_sets.append(set(t_j))
            
    # 4. If no R_j = 1 scans exist, the minimum number of infected terminals is 0.
    if not target_sets:
        print(0)
        return

    # 5. Optimization: Redundancy check for target_sets.
    # A scan is redundant if its set of possible infected terminals is a 
    # superset of another scan's set. Satisfying the subset satisfies the superset.
    target_sets.sort(key=len)
    minimal_sets = []
    for s in target_sets:
        for m in minimal_sets:
            if m.issubset(s):
                break
        else:
            minimal_sets.append(s)
    target_sets = minimal_sets
        
    K = len(possible_infected)
    M_prime = len(target_sets)
    
    # 6. For each potential infected terminal, create a bitmask of the scans it satisfies.
    # The j-th bit is 1 if terminal p satisfies the j-th minimal scan.
    satisfied_by_terminal = [0] * K
    for i, p in enumerate(possible_infected):
        for j, s in enumerate(target_sets):
            if p in s:
                satisfied_by_terminal[i] |= (1 << j)
    
    # Optimization: Filter out terminals that don't satisfy any remaining scan.
    satisfied_by_terminal = [x for x in satisfied_by_terminal if x > 0]
    K = len(satisfied_by_terminal)
    
    # target_all represents the bitmask where all minimal scans are satisfied.
    target_all = (1 << M_prime) - 1
    
    # 7. Use bitmask DP to find the minimum terminals needed to satisfy all scans.
    # dp[i] stores the union of scans satisfied by the subset of terminals represented by mask 'i'.
    # Since N <= 20, 2^N = 1,048,576 is small enough for this approach in Python.
    dp = [0] * (1 << K)
    ans = K
    
    for i in range(1, 1 << K):
        # Identify the lowest set bit to transition from a previously calculated state.
        lowbit = i & -i
        idx = lowbit.bit_length() - 1
        
        # Calculate scans satisfied by subset 'i' using the result of subset '(i without lowbit)'.
        val = dp[i ^ lowbit] | satisfied_by_terminal[idx]
        dp[i] = val
        
        # If the current subset satisfies all required scans, check its size.
        if val == target_all:
            cnt = bin(i).count('1')
            if cnt < ans:
                ans = cnt
    
    # Output the minimum number of infected terminals found.
    print(ans)

if __name__ == '__main__':
    solve()

This editorial was generated by gemini-3-flash-thinking.

投稿日時:
最終更新: