B - サンドイッチメロディ / Sandwich Melody Editorial by admin Editorial for printing
Gemini 3.0 Flash (Thinking)概要
この問題は、与えられた文字列 \(S\) の連続部分文字列のうち、その「圧縮列」が長さ \(3\) かつ \(1\) 文字目と \(3\) 文字目が一致するものの個数を求める問題です。
考察
圧縮列の性質と「サンドイッチ区間」の条件
文字列の圧縮列とは、連続する同じ文字を \(1\) 文字にまとめたものです。
例えば、AAABBAAAA の圧縮列は ABA となります。
問題文にある「サンドイッチ区間」の条件を整理すると、連続部分文字列 \(S[l..r]\) の圧縮列が \(X_1 X_2 X_3\) という形になり、かつ \(X_1 = X_3\) である必要があります。 これは、部分文字列が以下のような構造をしていることを意味します。
- 文字 \(A\) が \(1\) 回以上続く(ブロック1)
- 文字 \(B\) (\(B \neq A\)) が \(1\) 回以上続く(ブロック2)
- 文字 \(A\) が \(1\) 回以上続く(ブロック3)
そして、この部分文字列の直前や直後に他の文字が含まれていても、圧縮列の長さが「ちょうど \(3\)」であるためには、選んだ範囲 \([l, r]\) がちょうど \(3\) つの異なる文字のブロックにまたがっている必要があります。
効率的な数え上げ
\(N\) が最大 \(10^6\) であるため、すべての区間 \((l, r)\) を全探索すると \(O(N^2)\) かかり、間に合いません。そこで、文字列を同じ文字が連続する「ブロック」に分割して考えます(ランレングス圧縮)。
例えば、AAABBBAA は以下の \(3\) つのブロックに分けられます。
- ブロック 0: A が \(3\) 個
- ブロック 1: B が \(3\) 個
- ブロック 2: A が \(2\) 個
ここで、圧縮列が ABA となるような区間 \([l, r]\) は、「ブロック 0 のいずれかの位置から始まり、ブロック 1 をすべて含み、ブロック 2 のいずれかの位置で終わる」ような区間に他なりません。
一般に、連続する \(3\) つのブロック \(i, i+1, i+2\) において、ブロック \(i\) とブロック \(i+2\) の文字が同じである場合、以下の条件を満たす \((l, r)\) はすべてサンドイッチ区間になります。 - \(l\) はブロック \(i\) 内のどの位置でもよい。 - \(r\) はブロック \(i+2\) 内のどの位置でもよい。
このとき、ブロック \(i\) の長さを \(L_i\)、ブロック \(i+2\) の長さを \(L_{i+2}\) とすると、その組み合わせは \(L_i \times L_{i+2}\) 通り存在します。
アルゴリズム
- ランレングス圧縮:
文字列 \(S\) を走査し、同じ文字が連続する区間を「文字」と「その長さ」のリストに変換します。
例:
AAABBBAA\(\rightarrow\)[('A', 3), ('B', 3), ('A', 2)] - 三つ組の判定:
圧縮されたブロックのリストを先頭から順に見ていきます。
連続する \(3\) つのブロック \((i, i+1, i+2)\) について、
ブロック[i].文字 == ブロック[i+2].文字であるかを確認します。 - 個数の加算:
条件を満たす場合、
ブロック[i].長さ * ブロック[i+2].長さを答えに加算します。 - 結果の出力: 最終的な合計値を出力します。
計算量
- 時間計算量: \(O(N)\) 文字列を \(1\) 回走査してブロック化するのに \(O(N)\)、ブロックのリスト(最大 \(N\) 個)を \(1\) 回走査するのに \(O(N)\) かかるため、全体で \(O(N)\) です。
- 空間計算量: \(O(N)\) ブロック化したデータを格納するためのメモリが必要です。
実装のポイント
文字列の長さ \(N\) が大きいため、Python では
sys.stdin.read().split()などを用いて高速に入力を読み込むのが望ましいです。隣接するブロックは必ず異なる文字になるため、
ブロック[i].文字 == ブロック[i+2].文字が成り立つとき、自動的に真ん中のブロック[i+1]は異なる文字であることが保証されます。ソースコード
import sys
def solve():
# Read all input from standard input at once
input_data = sys.stdin.read().split()
if len(input_data) < 2:
return
# The first element is N, the length of the string
n = int(input_data[0])
# The string S may be split across multiple lines in some input formats,
# so we join all subsequent elements to form the complete string.
# For N=10^6, this is efficient enough in Python.
s = "".join(input_data[1:])
# Update n to be the actual length of the string s
n = len(s)
# A sandwich interval must have a compressed sequence of length exactly 3.
# This requires the substring to span at least 3 characters.
if n < 3:
print(0)
return
# To find all sandwich intervals, we first compress the string into blocks
# of identical consecutive characters. Each block is represented by its
# character and its length.
chars = []
lengths = []
if n > 0:
curr_char = s[0]
curr_len = 0
for char in s:
if char == curr_char:
curr_len += 1
else:
chars.append(curr_char)
lengths.append(curr_len)
curr_char = char
curr_len = 1
# Add the final block
chars.append(curr_char)
lengths.append(curr_len)
num_blocks = len(chars)
ans = 0
# A sandwich interval corresponds to a sequence of exactly three consecutive
# blocks (B_i, B_{i+1}, B_{i+2}) such that the character in the first block
# is the same as the character in the third block (chars[i] == chars[i+2]).
#
# Because adjacent blocks always have different characters, if chars[i] == chars[i+2],
# the character in the middle block (chars[i+1]) must be different.
# Thus, the compressed sequence for any substring starting in block i,
# covering all of block i+1, and ending in block i+2 will be
# (chars[i], chars[i+1], chars[i]). This sequence has length exactly 3
# and its 1st and 3rd characters are equal.
#
# The number of such intervals for a specific triple of blocks is the
# number of ways to choose a starting position in the first block (lengths[i])
# multiplied by the number of ways to choose an ending position in the
# third block (lengths[i+2]).
for i in range(num_blocks - 2):
if chars[i] == chars[i+2]:
ans += lengths[i] * lengths[i+2]
# Output the total count of sandwich intervals
print(ans)
if __name__ == '__main__':
solve()
この解説は gemini-3-flash-thinking によって生成されました。
posted:
last update: