公式

E - 看板の連結 / Concatenation of Signboards 解説 by admin

Claude 4.6 Opus (Thinking)

Overview

When connecting \(N\) signs in order, we want to maximally overlap adjacent signs where the “end of the previous sign” matches the “beginning of the next sign,” and find the total number of characters in the entire banner.

Analysis

Key Insight

For two adjacent strings \(S_{i-1}\) and \(S_i\), we want to find the maximum \(l\) such that the last \(l\) characters of \(S_{i-1}\) match the first \(l\) characters of \(S_i\). In other words, this is the problem of finding the length of the longest string that is both a suffix of \(S_{i-1}\) and a prefix of \(S_i\).

Issues with the Naive Approach

If we simply try all values of \(l\) and compare strings, it takes \(O(\min(|S_{i-1}|, |S_i|)^2)\) for each pair, which results in TLE when the strings are long.

Solution

We use the KMP (Knuth-Morris-Pratt) algorithm. By running KMP matching with \(S_i\) as the “pattern” and \(S_{i-1}\) as the “text,” the match length at the point when we finish scanning to the end of \(S_{i-1}\) corresponds to the desired maximum overlap length.

Algorithm

Computing Maximum Overlap Using KMP

  1. Building the failure function: Compute the KMP failure function for \(S_i\) (the pattern). The failure function \(\text{fail}[j]\) represents “the length of the longest proper prefix of the first \(j+1\) characters of \(S_i\) that is also a suffix.”

  2. Running KMP matching: Scan \(S_{i-1}\) (the text) from left to right, advancing the matching with \(S_i\). The match length \(k\) at the point when we reach the end of the text is the longest length where the end of \(S_{i-1}\) matches the beginning of \(S_i\).

  3. Handling constraints:

    • If \(k = |S_i|\) (complete match), this violates the constraint \(l < |S_i|\), so we fall back to \(k = \text{fail}[k-1]\).
    • If \(k \geq |S_{i-1}|\), this violates the constraint \(l < |S_{i-1}|\), so we similarly fall back using the failure function.

Concrete Example

For \(S_1 = \) abcabc, \(S_2 = \) abcxyz:

  • Build the failure function for \(S_2\): [0, 0, 0, 0, 0, 0]
  • Run KMP matching on \(S_1\):
    • a\(k=1\), b\(k=2\), c\(k=3\), a\(k=1\) (mismatch with x, fall back and re-match)…
    • In practice, the last 3 characters abc of abcabc match the first abc of \(S_2\), giving \(k=3\)
  • \(L_2 = 3\), total characters \(= 6 + (6 - 3) = 9\)

Computing Total Characters

\[\text{Total characters} = |S_1| + \sum_{i=2}^{N} (|S_i| - L_i)\]

Complexity

  • Time complexity: \(O\left(\sum_{i=1}^{N} |S_i|\right)\)
    • For each pair \((S_{i-1}, S_i)\), building the KMP failure function takes \(O(|S_i|)\) and matching takes \(O(|S_{i-1}|)\). Overall, each string is used at most twice (once as text and once as pattern), so the total is proportional to the sum of string lengths.
  • Space complexity: \(O\left(\max_i |S_i|\right)\) (for the failure function array)

Implementation Notes

  • Handling complete matches: If the pattern is completely matched during KMP matching (\(k = |S_i|\)), due to the constraint \(l < |S_i|\), we need to fall back to the next longest match length using \(k = \text{fail}[k-1]\).

  • Constraint \(l < |S_{i-1}|\): After matching is complete, if \(k \geq |S_{i-1}|\), we also need to fall back using the failure function to find the maximum value satisfying the constraint.

  • Fast input: In Python, use sys.stdin.buffer.read() to read all input at once, reducing I/O overhead.

    Source Code

import sys

def compute_kmp_failure(pattern):
    m = len(pattern)
    fail = [0] * m
    k = 0
    for i in range(1, m):
        while k > 0 and pattern[k] != pattern[i]:
            k = fail[k - 1]
        if pattern[k] == pattern[i]:
            k += 1
        fail[i] = k
    return fail

def find_max_overlap(s_prev, s_curr):
    # We want the longest l such that:
    # 1 <= l < len(s_prev) and l < len(s_curr)
    # s_prev ends with s_curr[:l]
    # We use KMP: treat s_curr as "pattern" and match against s_prev
    # But we only care about the suffix of s_prev matching a prefix of s_curr.
    
    # Build KMP failure function for s_curr
    # Then run KMP matching of s_curr against s_prev, and at the end of s_prev,
    # the current match length gives us the longest prefix of s_curr that matches
    # a suffix of s_prev.
    
    len_prev = len(s_prev)
    len_curr = len(s_curr)
    
    if len_prev == 0 or len_curr == 0:
        return 0
    
    # Build failure function for s_curr
    fail = compute_kmp_failure(s_curr)
    
    # Run KMP: match s_curr (as pattern) against s_prev (as text)
    k = 0  # number of characters of s_curr matched so far
    for i in range(len_prev):
        while k > 0 and s_curr[k] != s_prev[i]:
            k = fail[k - 1]
        if s_curr[k] == s_prev[i]:
            k += 1
        if k == len_curr:
            # Full match of s_curr found in s_prev; we need to back off
            # because l < len_curr is required
            k = fail[k - 1]
    
    # k is the length of the longest prefix of s_curr that matches
    # a suffix of s_prev. But we also need l < len_prev.
    # k < len_curr is guaranteed (we handled full match above).
    # We need k < len_prev as well.
    while k >= len_prev:
        if k == 0:
            break
        k = fail[k - 1]
    
    return k

def main():
    input_data = sys.stdin.buffer.read().decode()
    tokens = input_data.split()
    idx = 0
    N = int(tokens[idx]); idx += 1
    
    strings = []
    for i in range(N):
        strings.append(tokens[idx]); idx += 1
    
    total = len(strings[0])
    for i in range(1, N):
        overlap = find_max_overlap(strings[i-1], strings[i])
        total += len(strings[i]) - overlap
    
    print(total)

if __name__ == '__main__':
    main()

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

投稿日時:
最終更新: