B - 図書館の蔵書検索 / Library Book Search Editorial by admin Editorial for printing
GPT 5.2 HighOverview
For each search query \(T\), we count how many bookshelves have at least one book whose title contains \(T\) as a subsequence. The subsequence check is performed efficiently using a two-pointer method that scans each title only once.
Analysis
Key Observations
Subsequence checking can be done in \(O(|S|)\) by scanning the string \(S\) from left to right while sequentially looking for the next character of \(T\) to match.
Example: \(T=\)"abc", \(S=\)"axbyc"- Find
a→ next look forb - Find
b→ next look forc - Find
c→ all matched (it is a subsequence)
- Find
What we want is the number of bookshelves, not the number of books. So once at least one book in a bookshelf is a hit, there is no need to check the remaining books in that bookshelf (early termination is effective).
Why a Naive Approach is Dangerous
- If we perform subsequence checking using DP (e.g., \(dp[i][j]\)), it costs \(O(|S| \cdot |T|)\) per book. Since \(|S|, |T|\) can be up to 1000, a single check can take up to \(10^6\), and repeating this across many books and many queries will not fit within the time limit.
- On the other hand, the two-pointer method only costs \(O(|S|)\) per book, and since the total number of characters across all input strings is bounded by around \(10^5\), this remains practical even with up to \(Q \le 200\) queries.
How to Solve It
- For each query \(T\):
- Iterate over each bookshelf:
- For each title \(S\) in the bookshelf, use the two-pointer method to check whether \(T\) is a subsequence of \(S\)
- If even one book matches, count that bookshelf and move on to the next bookshelf (early termination)
- Iterate over each bookshelf:
- Additionally, when \(|T| > |S|\), \(T\) can never be a subsequence of \(S\), so we skip the check to avoid unnecessary work.
Algorithm
- Read the input and store titles as an array of strings for each bookshelf.
- Prepare a subsequence checking function
is_subseq(T, S)(two-pointer method):- Maintain an index \(i = 0\) for \(T\)
- Scan \(S\) from left to right; when a character of \(S\) matches \(T[i]\), increment \(i\)
- If \(i == |T|\), all characters have been matched, so return True
- If we reach the end of \(S\) without matching all characters, return False
- For each query \(T\), do the following:
cnt = 0- For each bookshelf, check titles one by one; if
is_subseq(T, S)is True for any book, docnt += 1and move to the next bookshelf (break)
- Output
cntfor each query.
Complexity
- Time complexity: At most \(O\!\left(Q \cdot \sum |S|\right)\)
(The upper bound is scanning all titles once per query. If a hit occurs within a bookshelf, it will be less than this.) - Space complexity: \(O\!\left(\sum |S| + \sum |T|\right)\)
(For storing the input strings)
Implementation Notes
Two-pointer subsequence check: It only scans \(S\) once, making it fast.
Length check for pruning: Only call the check function when \(|T| \le |S|\) to avoid unnecessary work.
Early termination per bookshelf: Once one book is a hit, that bookshelf is confirmed, so skip the remaining titles (
break).Fast I/O: Using
sys.stdin.buffer.read().split()and handling strings asbytesmakes comparisons faster.Source Code
import sys
def is_subseq(t: bytes, s: bytes) -> bool:
m = len(t)
if m > len(s):
return False
i = 0
ti = t[0]
for ch in s:
if ch == ti:
i += 1
if i == m:
return True
ti = t[i]
return False
def main():
data = sys.stdin.buffer.read().split()
it = iter(data)
N = int(next(it))
shelves = []
for _ in range(N):
k = int(next(it))
titles = [next(it) for _ in range(k)] # bytes
shelves.append(titles)
Q = int(next(it))
queries = [next(it) for _ in range(Q)] # bytes
sub = is_subseq
out_lines = []
for t in queries:
cnt = 0
lt = len(t)
for titles in shelves:
hit = False
for s in titles:
if lt <= len(s) and sub(t, s):
hit = True
break
if hit:
cnt += 1
out_lines.append(str(cnt))
sys.stdout.write("\n".join(out_lines))
if __name__ == "__main__":
main()
This editorial was generated by gpt-5.2-high.
posted:
last update: