Official

E - 登山道の快適さ / Comfort of the Mountain Trail Editorial by admin

Claude 4.6 Opus (Thinking)

概要

登山道上の隣接チェックポイント間の標高差に対して、点更新と区間クエリ(最大値、ただし最大値が閾値 \(K\) を超える場合は1つ除外可能)を高速に処理する問題。セグメント木で最大値・第2最大値を管理して解く。

考察

疲労度の計算を整理する

まず \(d_i = |A_i - A_{i+1}|\)(隣接標高差)を定義すると、区間 \([l, r]\) の疲労度は \(d_l, d_{l+1}, \ldots, d_{r-1}\) に対する操作で決まります。

  • \(M_1 = \max(d_l, \ldots, d_{r-1})\) とする
  • \(M_1 \le K\) なら答えは \(M_1\)
  • \(M_1 > K\) なら、集合からちょうど1つの要素を除外して残りの最大値を最小化する

除外の最適戦略

「1つ除外して残りの最大値を最小化」するには、最大値 \(M_1\) を持つ要素を除外するのが最適です。除外後の最大値は:

  • \(M_1\) の値を持つ要素が2個以上ある → 1つ除いても最大値は \(M_1\) のまま
  • \(M_1\) の値を持つ要素がちょうど1個 → 除外後の最大値は第2最大値 \(M_2\)

素朴なアプローチの問題点

各クエリで区間を走査すると \(O(N)\) かかり、\(Q\) 回のクエリで \(O(NQ)\) となりTLEします。

解決策

セグメント木の各ノードに (最大値 \(M_1\), その出現回数 \(c_1\), 第2最大値 \(M_2\)) の3つ組を持たせることで、区間の情報を \(O(\log N)\) で取得できます。

アルゴリズム

  1. 前処理: \(d_i = |A_i - A_{i+1}|\)(\(i = 1, \ldots, N-1\))を計算し、セグメント木を構築する。

  2. セグメント木のノード情報: \((M_1, c_1, M_2)\)

    • \(M_1\): 区間内の最大値
    • \(c_1\): \(M_1\) の出現回数
    • \(M_2\): \(M_1\) より真に小さい最大の値(存在しなければ \(-1\))
  3. ノードのマージ: 2つの子ノード \((a_{M1}, a_{c1}, a_{M2})\) と \((b_{M1}, b_{c1}, b_{M2})\) を統合:

    • \(a_{M1} > b_{M1}\) → \(M_1 = a_{M1}\), \(c_1 = a_{c1}\), \(M_2 = \max(a_{M2}, b_{M1})\)
    • \(a_{M1} < b_{M1}\) → \(M_1 = b_{M1}\), \(c_1 = b_{c1}\), \(M_2 = \max(b_{M2}, a_{M1})\)
    • \(a_{M1} = b_{M1}\) → \(M_1 = a_{M1}\), \(c_1 = a_{c1} + b_{c1}\), \(M_2 = \max(a_{M2}, b_{M2})\)
  4. イベント1(更新): \(A_p\) を変更後、\(d_{p-1}\) と \(d_p\) を再計算してセグメント木を点更新。

  5. イベント2(クエリ): 区間 \([l, r-1]\) のノード情報 \((M_1, c_1, M_2)\) を取得し:

    • \(l = r\) → 答えは \(0\)
    • \(M_1 \le K\) → 答えは \(M_1\)
    • \(M_1 > K\) かつ区間長1(\(r = l+1\))→ 答えは \(0\)
    • \(M_1 > K\) かつ \(c_1 \ge 2\) → 答えは \(M_1\)(除外しても最大値は変わらない)
    • \(M_1 > K\) かつ \(c_1 = 1\) → 答えは \(\max(M_2, 0)\)

計算量

  • 時間計算量: \(O((N + Q) \log N)\)(構築 \(O(N)\)、各クエリ・更新 \(O(\log N)\))
  • 空間計算量: \(O(N)\)(セグメント木のサイズ)

実装のポイント

  • セグメント木のサイズは \(N-1\) 個の要素(\(d_1, \ldots, d_{N-1}\))に対して構築する。2冪に切り上げる。

  • 空ノードを表す特別な値 (-1, 0, -1) を用意し、マージ時に片方が空なら他方をそのまま返す。

  • \(A_p\) を変更すると \(d_{p-1}\) と \(d_p\) の最大2箇所が影響を受けるため、範囲チェックして両方更新する。

  • \(M_2\) が存在しない場合(\(-1\))に備えて、最終出力時に \(\max(M_2, 0)\) とする(疲労度は非負)。

    ソースコード

import sys
from math import inf

def main():
    input_data = sys.stdin.buffer.read().split()
    idx = 0
    N = int(input_data[idx]); idx += 1
    Q = int(input_data[idx]); idx += 1
    K = int(input_data[idx]); idx += 1
    
    A = [0] * (N + 1)
    for i in range(1, N + 1):
        A[i] = int(input_data[idx]); idx += 1
    
    # d[i] = |A[i] - A[i+1]| for i = 1..N-1
    # We need a segment tree on d[1..N-1] that supports:
    # - point update
    # - range query: given [l, r-1], find:
    #   1) max of range (M1)
    #   2) second max of range (M2) — the max after removing the element achieving M1
    #      More precisely: if M1 > K, we remove exactly one element to minimize the resulting max.
    #      The optimal strategy is to remove an element with value = M1.
    #      After removal, the max is the second largest value, i.e., max of all elements except
    #      one occurrence of M1. But if all elements equal M1, removing one still leaves M1 
    #      (unless there's only one element).
    #
    # Actually, let's think more carefully:
    # We want min over j of (max of S \ {j}).
    # To minimize, we should remove the element with the largest d value.
    # After removing the max element, the new max is:
    #   - If there are >= 2 elements with value = max, then still max.
    #   - If there's exactly 1 element with value = max, then second max.
    #
    # So we need: max1, max2 (first and second maximum), and count of max1.
    
    # Segment tree storing (max1, count_of_max1, max2) for each node
    # max2 is the largest value strictly less than max1, or -inf if none
    
    sz = 1
    while sz < (N - 1):
        sz *= 2
    
    # Each node: (max1, cnt1, max2)
    EMPTY = (-1, 0, -1)  # represents empty interval
    
    tree = [EMPTY] * (2 * sz)
    
    def make_leaf(val):
        return (val, 1, -1)
    
    def combine(a, b):
        if a[0] == -1 and a[1] == 0:
            return b
        if b[0] == -1 and b[1] == 0:
            return a
        am1, ac1, am2 = a
        bm1, bc1, bm2 = b
        if am1 > bm1:
            m1 = am1
            c1 = ac1
            m2 = max(am2, bm1)
        elif am1 < bm1:
            m1 = bm1
            c1 = bc1
            m2 = max(bm2, am1)
        else:
            m1 = am1
            c1 = ac1 + bc1
            m2 = max(am2, bm2)
        return (m1, c1, m2)
    
    # Build
    d = [0] * (N)  # d[i] for i=1..N-1, stored at d[i]
    for i in range(1, N):
        d[i] = abs(A[i] - A[i + 1])
    
    for i in range(1, N):
        tree[sz + i - 1] = make_leaf(d[i])
    
    for i in range(sz - 1, 0, -1):
        tree[i] = combine(tree[2 * i], tree[2 * i + 1])
    
    def update(pos, val):
        # pos is 1-indexed in d array
        pos = pos - 1 + sz  # 0-indexed in tree leaves
        tree[pos] = make_leaf(val)
        pos >>= 1
        while pos >= 1:
            tree[pos] = combine(tree[2 * pos], tree[2 * pos + 1])
            pos >>= 1
    
    def query(l, r):
        # query d[l..r], 1-indexed
        if l > r:
            return EMPTY
        l = l - 1 + sz
        r = r - 1 + sz
        res_l = EMPTY
        res_r = EMPTY
        while l <= r:
            if l & 1:
                res_l = combine(res_l, tree[l])
                l += 1
            if not (r & 1):
                res_r = combine(tree[r], res_r)
                r -= 1
            l >>= 1
            r >>= 1
        return combine(res_l, res_r)
    
    out = []
    for _ in range(Q):
        t = int(input_data[idx]); idx += 1
        if t == 1:
            p = int(input_data[idx]); idx += 1
            v = int(input_data[idx]); idx += 1
            A[p] = v
            if p - 1 >= 1:
                d[p - 1] = abs(A[p - 1] - A[p])
                update(p - 1, d[p - 1])
            if p + 1 <= N:
                d[p] = abs(A[p] - A[p + 1])
                update(p, d[p])
        else:
            l = int(input_data[idx]); idx += 1
            r = int(input_data[idx]); idx += 1
            if l == r:
                out.append(0)
            elif r == l + 1:
                val = abs(A[l] - A[r])
                if val <= K:
                    out.append(val)
                else:
                    out.append(0)
            else:
                # query d[l..r-1]
                m1, c1, m2 = query(l, r - 1)
                if m1 <= K:
                    out.append(m1)
                else:
                    # remove one element with value m1
                    if c1 >= 2:
                        out.append(m1)
                    else:
                        out.append(max(m2, 0))
    
    sys.stdout.write('\n'.join(map(str, out)) + '\n')

main()

この解説は claude4.6opus-thinking によって生成されました。