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

あっとのTECH LOG

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

JOI難易度6 つらら

問題原文

https://www.ioi-jp.org/joi/2009/2010-ho-prob_and_sol/2010-ho.pdf#page=7

問題要旨

 N 本のつららが横一列に並んでいる。  i 番目のつららの長さは  a_i である。
つららは、両隣のつららよりも自身が長い時 に、毎秒1cmずつ伸び、  L cmになると折れる。(折れたつららは以後 0cmとして扱う)
全てのつららが折れるまでにかかる時間は何秒か?なお、最初隣り合うつららの長さは相異なる。

  •   2 \leq N \leq 10^{5}
  •  2 \leq L \leq 50000

解法

いろんな方法があると思うけど、メモ化再帰が直感的だと感じる。
例えば、あるつらら  i に着目した時、そのつららが両隣のどちらよりも小さい時、そのつららが折れるまでの秒数は、
max( i -1 番目のつららが折れるまでの秒数,  i + 1 番目のつららが折れるまでの秒数) +  L -  a_i になる。

実装

番兵を置いとくと少し楽。

import sys
sys.setrecursionlimit(10 ** 9)

N, L = map(int, input().split())
A = [int(input()) for _ in range(N)]
MIN = min(A)
A = [-1] + A + [-1]  # 番兵追加

memo = [-1] * len(A)

# 左からi番目のつららが折れるまで何秒かかるか
def calc(i):
    if memo[i] > 0:
        return memo[i]

    if (A[i] > A[i - 1]) and (A[i] > A[i + 1]):
        ret = max(0, L - A[i])

    elif A[i - 1] < A[i] < A[i + 1]:
        ret = max(0, L - A[i] + calc(i + 1))

    elif A[i + 1] < A[i] < A[i - 1]:
        ret = max(0, L - A[i] + calc(i - 1))

    else:
        ret = max(0, L - A[i] + max(calc(i - 1), calc(i + 1)))

    memo[i] = ret
    return ret

for i in range(1, N + 1):
    memo[i] = calc(i)

print(max(memo))

感想

かなり好きな問題。
他の方法でも今度解いてみよう。。。