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
\(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”.
\(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
Bitmask representation: Map terminal \(i\) to bit \(i-1\), and convert each scan’s target terminal set into a bitmask.
Compute forbidden terminals: For all scans with \(R_j = 0\), aggregate the target terminal bits using OR to create a
forbiddenmask. The infection candidates (allowed) are the complement of this mask.Collect \(R_j = 1\) constraints: For each “infection detected” scan, AND its target mask with
allowedand store the result in apositive_maskslist.Exhaustive enumeration: Enumerate all subsets of
allowed. For each subset \(s\), check whether \((s \mathbin{\&} pm) \neq 0\) holds for allpositive_masks. If so, record the bit count (popcount) and update the minimum.- Use the technique
s = (s - 1) & allowedto efficiently iterate only over subsets ofallowed.
- Use the technique
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
allowedis 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) & allowedenumerates subsets ofallowedin descending order. To include \(0\) without omission, checks == 0at 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.
投稿日時:
最終更新: