公式

B - 図書館の蔵書検索 / Library Book Search 解説 by admin

Gemini 3.0 Flash (Thinking)

Overview

This problem asks us to determine whether a given query string \(T\) is a “subsequence” of any book title \(S\) on each bookshelf, and to find the total number of bookshelves that contain at least one book satisfying the condition.

Analysis

First, considering a naive approach, for each query we would check “all bookshelves × all titles within them.” Looking at the constraints: number of queries \(Q \le 200\), total number of books \(\sum K_i \le 5000\), and maximum string length is \(1000\). If we perform subsequence checking with simple loops, the worst case requires approximately \(200 \times 5000 \times 1000 = 10^9\) operations, which may not fit within the time limit.

Therefore, we aim for efficiency by combining the following optimizations:

  1. Deduplication of books and bookshelf management: The same title may appear on multiple bookshelves or multiple times on the same bookshelf. By using titles as keys and maintaining a “list of bookshelves containing that book,” we reduce the number of checks.
  2. Fast skip checks (pruning): For a title \(S\) to contain query \(T\) as a subsequence, it must satisfy at least the following conditions:
    • \(|S| \geq |T|\) (the title length is at least as long as the query)
    • All characters in \(T\) also appear in \(S\) The latter can be checked very efficiently by managing the 26 lowercase English letters as bit flags (bitmask).
  3. Early termination per bookshelf: For a given query, if bookshelf \(i\) is already known to be a “hit,” there is no need to check other books on that bookshelf. Additionally, processing for the query can be terminated once all bookshelves have been hit.

Algorithm

  1. Preprocessing:
    • Scan all book titles and record the following information for each unique title:
      • List of bookshelf indices containing that title
      • Length of the title
      • Set of characters in the title (bitmask: bit 0 for a, bit 1 for b, etc.)
  2. Query Processing: For each query \(T\), do the following:
    • Create the bitmask for query \(T\).
    • Prepare an array shelf_hits (of length \(N\)) to track whether each bookshelf has been hit.
    • Examine unique titles in order:
      1. If all bookshelves containing that title are already marked True in shelf_hits, skip the check.
      2. If the title length is shorter than \(T\), or the bitmask indicates that \(S\) is missing characters present in \(T\), skip the check.
      3. Perform the actual subsequence check (greedy method).
      4. If it is a subsequence, mark all bookshelves containing that title as True in shelf_hits and update the hit count.
      5. If all bookshelves have been hit (count \(= N\)), break out of the loop for that query.
  3. Efficient subsequence checking: In Python, the following approach using iter() is very fast:
    
    s_iter = iter(S)
    all(char in s_iter for char in T)
    
    This concisely expresses the “greedy method” where for each character in \(T\), we advance through the iterator of \(S\) to find the character.

Complexity

  • Time Complexity: \(O(\sum |S_{i,j}| + Q \cdot (\text{UniqueBooks} \times \text{CheckCost}))\)
    • Preprocessing takes time proportional to the total string length.
    • For each query, powerful pruning via bitmasks and other techniques greatly reduces the number of expensive subsequence checks actually performed. Even in the worst case, the values of \(N\) and \(Q\) are small enough to fit within the time limit.
  • Space Complexity: \(O(\sum |S_{i,j}|)\)
    • Memory is needed to store unique titles and their associated information.

Implementation Notes

  • Bitmask: Use ord(char) - ord('a') to compute bit positions from 0 to 25.

  • Early termination: The key point is to avoid unnecessary subsequence checks by determining whether all bookshelves containing a given book have already been checked.

  • Caching: If the same query may be submitted multiple times, caching query results in a dictionary provides additional safety.

    Source Code

import sys

def solve():
    # Fast input reading
    input_data = sys.stdin.read().split()
    if not input_data:
        return
    
    ptr = 0
    N = int(input_data[ptr])
    ptr += 1
    
    # Pre-process unique books across all bookshelves
    # unique_books map: title -> [bitmask, length, [list of shelf indices]]
    unique_books = {}
    for i in range(N):
        K = int(input_data[ptr])
        ptr += 1
        
        # Use a set to handle duplicate titles within the same shelf
        shelf_titles = set()
        for _ in range(K):
            shelf_titles.add(input_data[ptr])
            ptr += 1
        
        for s in shelf_titles:
            if s not in unique_books:
                # Precompute bitmask of characters in the title
                m = 0
                for char in s:
                    m |= (1 << (ord(char) - 97))
                unique_books[s] = [m, len(s), [i]]
            else:
                # Append the current shelf index to the list of shelves containing this title
                unique_books[s][2].append(i)
    
    # Convert unique_books to a list for efficient iteration
    # Structure: (title, bitmask, length, shelf_indices)
    book_list = []
    for s, (m, length, indices) in unique_books.items():
        book_list.append((s, m, length, indices))
    
    # Sort books to improve the probability of hitting bookshelves early
    # Prioritize longer books and books appearing in more shelves
    book_list.sort(key=lambda x: (x[2], len(x[3])), reverse=True)
    
    # Read the number of queries
    if ptr >= len(input_data):
        return
    Q_count = int(input_data[ptr])
    ptr += 1
    
    query_cache = {}
    results = []
    
    for _ in range(Q_count):
        if ptr >= len(input_data):
            break
        T = input_data[ptr]
        ptr += 1
        
        # If the same query has been processed before, use the cached result
        if T in query_cache:
            results.append(query_cache[T])
            continue
        
        # Precompute bitmask for the query string
        mask_T = 0
        for char in T:
            mask_T |= (1 << (ord(char) - 97))
        
        len_T = len(T)
        shelf_hits = [False] * N
        hits_count = 0
        
        for s, mask_S, length_S, indices in book_list:
            # If all bookshelves are already hit, stop checking further books
            if hits_count == N:
                break
            
            # Check if this book belongs to any shelf that hasn't been hit yet
            already_hit = True
            for idx in indices:
                if not shelf_hits[idx]:
                    already_hit = False
                    break
            if already_hit:
                continue
            
            # Fast filtering using length and character existence (bitmask)
            if length_S < len_T or (mask_T & mask_S) != mask_T:
                continue
            
            # Efficient greedy subsequence check using iterators
            s_iter = iter(s)
            if all(c in s_iter for c in T):
                # If T is a subsequence, mark all shelves containing this book as hit
                for idx in indices:
                    if not shelf_hits[idx]:
                        shelf_hits[idx] = True
                        hits_count += 1
        
        res_str = str(hits_count)
        query_cache[T] = res_str
        results.append(res_str)
    
    # Fast output using sys.stdout.write
    sys.stdout.write('\n'.join(results) + '\n')

if __name__ == '__main__':
    solve()

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

投稿日時:
最終更新: