公式

E - 花壇の区間選び / Choosing Flowerbed Intervals 解説 by admin

Claude 4.6 Opus (Thinking)

概要

一列に並んだ \(N\) 本の花から、「品種の多様さ」と「高さのバランス」に関する2つの条件を同時に満たす連続区間 \([l, r]\) の個数を数える問題です。尺取り法(two-pointer)を2回使って効率的に解きます。

考察

重要な気づき:単調性

固定した左端 \(l\) に対して、右端 \(r\) を増やしていくとき:

  • 条件1\(D \times (r - l + 1)\)\(D\)(異なる品種数)は非減少、区間長 \((r-l+1)\) は単調増加なので、積全体も非減少
  • 条件2\(\max B_i - \min B_i\):区間が広がると最大値は増え最小値は減るので、差は非減少

つまり、各 \(l\) に対して「条件1を満たす最大の \(r\)」と「条件2を満たす最大の \(r\)」がそれぞれ存在し、両方を満たす \(r\) の範囲は \([l, \min(r_1[l], r_2[l])]\) となります。

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

全ての区間を調べると \(O(N^2)\) 個あり、各区間で条件判定に \(O(N)\) かかると \(O(N^3)\) で TLE になります。

解決策

単調性を利用して、\(l\) を左から右に動かしたとき、\(r_1[l]\)\(r_2[l]\) も右方向にしか動かないことを利用し、尺取り法\(O(N)\) で各 \(r_1[l], r_2[l]\) を求めます。

アルゴリズム

ステップ1:条件2の尺取り法

  • 区間の最大値・最小値を効率的に管理するため、単調デック(deque) を2本使います。
    • max_deq:値が単調減少となるようにインデックスを保持(先頭が最大値のインデックス)
    • min_deq:値が単調増加となるようにインデックスを保持(先頭が最小値のインデックス)
  • \(l\) を固定して \(r\) を伸ばし、\(\max - \min > M\) になったら停止。そのときの \(r-1\)\(r_2[l]\) です。

ステップ2:条件1の尺取り法

  • 区間内の異なる品種数 \(D\) を管理するため、品種ごとの出現頻度を辞書で管理します。
  • \(r\) を伸ばすとき、新しい要素の追加で \(D\) が増えるかを確認し、\(D \times (r - l + 1) > K\) になったら停止。

ステップ3:答えの集計

\(l\) に対して: $\(\text{ans} += \min(r_1[l],\, r_2[l]) - l + 1\)$

(ただし \(\min(r_1[l], r_2[l]) \ge l\) の場合のみ加算)

計算量

  • 時間計算量: \(O(N)\)
    • 各尺取り法で左端・右端ともに合計 \(O(N)\) 回しか動かないため
  • 空間計算量: \(O(N)\)
    • デック、頻度辞書、配列 \(r_1, r_2\)\(O(N)\)

実装のポイント

  • デックの管理\(l\) を右に進めるとき、デックの先頭が \(l\) と等しければ除去する必要があります。

  • right の初期化\(l\)\(right\) を追い越した場合(\(right < l\) になった場合)、データ構造をリセットして \(right = l - 1\) から再スタートします。

  • オーバーフロー注意\(K\) が最大 \(10^{18}\) なので、\(D \times (r-l+1)\) の計算で大きな値を扱います。Python では整数オーバーフローがないため問題ありませんが、C++ などでは注意が必要です。

  • 2つの尺取り法を独立に行う:条件1と条件2は独立に最大 \(r\) を求められるため、別々に処理してから最後に \(\min\) を取る設計がシンプルです。

    ソースコード

import sys
from collections import defaultdict

def solve():
    input_data = sys.stdin.buffer.read().split()
    idx = 0
    N = int(input_data[idx]); idx += 1
    K = int(input_data[idx]); idx += 1
    M = int(input_data[idx]); idx += 1
    A = [int(input_data[idx + i]) for i in range(N)]; idx += N
    B = [int(input_data[idx + i]) for i in range(N)]; idx += N

    # For each l, we need to find the maximum r such that both conditions hold.
    # Condition 1: D * (r - l + 1) <= K where D = number of distinct values in A[l..r]
    # Condition 2: max(B[l..r]) - min(B[l..r]) <= M
    
    # Neither condition is simply monotone in a way that allows a single two-pointer,
    # because condition 1 involves D * length which can increase non-monotonically
    # (D increases at certain points, length increases always).
    
    # However, for a fixed l, as r increases:
    # - D is non-decreasing
    # - (r - l + 1) is strictly increasing
    # - max - min is non-decreasing
    # So D * (r-l+1) is non-decreasing, and max-min is non-decreasing.
    # Both conditions define a prefix of valid r values for each l.
    # So the set of valid r for each l is [l, min(r1, r2)] where r1 is max r for cond1, r2 for cond2.
    
    # Condition 2 can be handled with two-pointer + deques for min/max.
    # Condition 1: D * (r - l + 1) <= K. As r increases, D*(r-l+1) is non-decreasing,
    # so we can use two-pointer for this too.
    
    # Two separate two-pointers, then for each l, answer += min(r1_limit, r2_limit) - l + 1
    
    # But we need to be careful: when l moves right, the right pointer for each condition
    # can only move right or stay (standard two-pointer property).
    
    # Let's compute for each l, the maximum r satisfying condition 1 (r1[l])
    # and condition 2 (r2[l]), then answer = sum of max(0, min(r1[l], r2[l]) - l + 1).
    
    # Condition 2: two-pointer with deques
    from collections import deque
    
    r2 = [0] * N  # max r (inclusive) for condition 2
    max_deq = deque()  # decreasing deque of indices
    min_deq = deque()  # increasing deque of indices
    right = -1
    for l in range(N):
        if right < l:
            right = l - 1
            max_deq.clear()
            min_deq.clear()
        while right + 1 < N:
            nr = right + 1
            # Check if adding nr keeps condition 2
            new_max = B[nr] if not max_deq else max(B[max_deq[0]], B[nr])
            new_min = B[nr] if not min_deq else min(B[min_deq[0]], B[nr])
            if new_max - new_min <= M:
                while max_deq and B[max_deq[-1]] <= B[nr]:
                    max_deq.pop()
                max_deq.append(nr)
                while min_deq and B[min_deq[-1]] >= B[nr]:
                    min_deq.pop()
                min_deq.append(nr)
                right += 1
            else:
                break
        r2[l] = right
        # Remove l from deques
        if max_deq and max_deq[0] == l:
            max_deq.popleft()
        if min_deq and min_deq[0] == l:
            min_deq.popleft()
    
    # Condition 1: two-pointer with frequency count for distinct
    r1 = [0] * N
    freq = defaultdict(int)
    distinct = 0
    right = -1
    for l in range(N):
        if right < l:
            right = l - 1
            freq.clear()
            distinct = 0
        while right + 1 < N:
            nr = right + 1
            new_distinct = distinct + (1 if freq[A[nr]] == 0 else 0)
            length = nr - l + 1
            if new_distinct * length <= K:
                if freq[A[nr]] == 0:
                    distinct += 1
                freq[A[nr]] += 1
                right += 1
            else:
                break
        r1[l] = right
        # Remove l
        freq[A[l]] -= 1
        if freq[A[l]] == 0:
            distinct -= 1
    
    ans = 0
    for l in range(N):
        valid_r = min(r1[l], r2[l])
        if valid_r >= l:
            ans += valid_r - l + 1
    
    print(ans)

solve()

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

投稿日時:
最終更新: