AT274の技術だったりなかったり

あっとのTECH LOG

競プロのこととか技術のこととか。たまに日常を書きます。

JOI難易度6 連鎖

問題原文

atcoder.jp

問題要旨

 N 個のぷよが縦に並んでいる。ぷよは同色が4つ以上連なると消える。
あるぷよの塊が消えると、その上にいたぷよたちは下に移動し、そのあとも消える条件を満たすのであれば連鎖的に消える。
あるぷよの1つの色を変更することで連鎖を起こす時、最小で最終的に残るぷよをいくつにできるか?

  •   1 \leq N \leq 10000
  • 最初同じ色のぷよは4つ以上連なっていない。

解法

ある変更を及ぼして連鎖が起きる場合というのは、11[22322]11のように消えた塊の隣の色が揃っていて、かつ4つ以上連続するときのみ。
よって、ある塊から左側のどこまで消えるかと、右側のどこまで消えるかを各塊について計算すればよい。
これだけなんだけど、実装は結構気をつける必要がある。

実装

まず、 11[223222]11のような色を変えたことによって消える最初の塊の長さが5以上になる場合に注意。
また、連鎖では左側と右側のマーカーを管理するが、連鎖で実際に消せる(塊の長さが4以上)状態にならないと、更新できないことに注意。

下の実装は割と冗長とはおもうけれど、わかりやすいのでとりあえずこれで。。。

from collections import Counter
N = int(input())
P = [int(input()) for _ in range(N)]

ans = N
for i in range(N - 4 + 1):
    delete = P[i: i + 4]
    color, cnt = Counter(delete).most_common(1)[0]

    # 1 2 1 1, 1 2 1 1, 1 1 2 1, 1 1 1 2 の形でないと色を変えても消せない
    if cnt != 3:
        continue

    # i, i + 1, i + 2, i + 3 は揃っている。色を変えたことで最初に消える塊の長さが5つ以上の場合に対応
    l, r = i - 1, i + 4
    while (l >= 0) and (P[l] == color):
        l -= 1
    while (r < N) and (P[r] == color):
        r += 1

    ans = min(ans, N - (r - l - 1))

    # 連鎖の処理
    while (l >= 0) and (r < N):
        cnt = 0
        if P[l] == P[r]:
            color = P[l]
            while (l >= 0) and (P[l] == color):
                l -= 1
                cnt += 1
            while (r < N) and (P[r] == color):
                r += 1
                cnt += 1

        if cnt < 4:
            break
        else:
            # 4つ以上連鎖するときでないと更新は起こらない!!!
            ans = min(ans, N - (r - l - 1))

print(ans)

感想

連鎖時に動く左と右のマーカーは、未確定状態のマーカーを示す変数を別に持った方がよかったかも。。。
見事にハマってしまった。