E - 座席と弁当の確認 / Checking Seats and Lunches Editorial by admin
Gemini 3.0 Flash (Thinking)概要
\(N\) 台の各バスについて「弁当が足りているか」を判定し、与えられた \(M\) 個の区間 \([L_j, R_j]\) 内に「弁当が足りないバス」が 1 台も存在しないかを高速に判定する問題です。
考察
1. 個別のバスの判定
まず、各バス \(i\) が「配布可能」か「配布不可」かは、与えられた \(S_i\) と \(P_i\) を比較することで簡単にわかります。 - \(S_i \geq P_i\) ならば配布可能(OK) - \(S_i < P_i\) ならば配布不可(NG)
2. 素朴なアプローチ
各休憩プラン(クエリ)に対して、毎回 \(L_j\) から \(R_j\) までのバスを 1 台ずつ確認していく方法が考えられます。 しかし、この方法では 1 つのクエリにつき最大で \(N\) 回の確認が必要になります。クエリの数は \(M\) 個あるため、最悪の場合の計算量は \(O(N \times M)\) となります。 本問題では \(N, M \leq 2 \times 10^5\) であるため、計算回数は最大で \(4 \times 10^{10}\) 回に達し、制限時間内に終わりません。
3. 累積和による高速化
「区間内に NG なバスが 1 台でも存在するか」を高速に判定するために、累積和の考え方を利用します。
まず、各バスの状態を次のような数値に置き換えます。 - 配布不可(NG)なバス \(i\) の場合: \(A_i = 1\) - 配布可能(OK)なバス \(i\) の場合: \(A_i = 0\)
このとき、ある区間 \([L, R]\) 内に配布不可なバスが存在するかどうかは、「区間内の \(A_i\) の総和が 1 以上であるか」を調べることと同義になります。
区間の総和を高速に求めるために、あらかじめ累積和配列 \(pref\) を作成します。 - \(pref[k] = A_1 + A_2 + \dots + A_k\) (先頭から \(k\) 台目までの NG バスの総数)
これを用意しておけば、任意の区間 \([L, R]\) における NG バスの個数は以下の計算により \(O(1)\) で求められます。 - 区間内の NG 数 \(= pref[R] - pref[L-1]\)
この値が \(0\) であればそのプランは Yes(配布可能)、\(1\) 以上であれば No(配布不可)と判定できます。
アルゴリズム
- 各バス \(i = 1, \dots, N\) について、\(S_i < P_i\) かどうかを判定する。
- 判定結果を \(0\) または \(1\) の配列 \(A\) とし、その累積和配列 \(pref\) を構築する。
- 各クエリ \((L_j, R_j)\) に対して、\(pref[R_j] - pref[L_j-1]\) を計算する。
- 計算結果が \(0\) なら
Yes、そうでなければNoを出力する。
計算量
- 時間計算量: \(O(N + M)\)
- 累積和の構築に \(O(N)\)、各クエリの回答に \(O(1)\) かかるため、全体で \(O(N + M)\) となり、十分に高速です。
- 空間計算量: \(O(N)\)
- 入力データおよび累積和配列を保持するために \(O(N)\) のメモリを使用します。
実装のポイント
高速な入出力: Python では \(N, M\) が \(2 \times 10^5\) 規模になると、標準の
input()では間に合わないことがあります。sys.stdin.read().split()などを使って一括で読み込むのが効果的です。インデックスの調整: 問題文は 1-indexed(1始まり)ですが、プログラムの配列は 0-indexed(0始まり)であることが多いため、累積和の範囲指定(特には左端 \(L-1\))に注意が必要です。
ソースコード
import sys
def solve():
# 競技プログラミングにおいて、入力を一度に読み込んで処理するのは高速化の一般的な手法です。
# sys.stdin.read().split() は、すべての空白文字(スペース、改行など)で分割された文字列のリストを返します。
input_data = sys.stdin.read().split()
if not input_data:
return
# すべての入力文字列を整数のリストに変換します。
# これにより、インデックスによる高速なアクセスが可能になります。
data = list(map(int, input_data))
# バスの台数 N と休憩プランの数 M を取得します。
N = data[0]
M = data[1]
# 弁当の数 S_i と生徒の人数 P_i のデータが始まる位置(オフセット)を設定します。
# S_1, S_2, ..., S_N は data[2] から data[N+1] まで
# P_1, P_2, ..., P_N は data[N+2] から data[2N+1] までに格納されています。
s_offset = 2
p_offset = 2 + N
# 各バスにおいて弁当が足りない(S_i < P_i)かどうかを判定し、累積和を計算します。
# pref[k] は、バス 1 からバス k までの間で弁当が不足しているバスの総数を表します。
# これにより、任意の区間 [L, R] における不足バスの有無を O(1) で判定できます。
pref = [0] * (N + 1)
for i in range(N):
# バス (i+1) が弁当不足(配布不可)かどうかをチェック
if data[s_offset + i] < data[p_offset + i]:
pref[i+1] = pref[i] + 1
else:
pref[i+1] = pref[i]
# クエリ(休憩プラン)が始まる位置を設定します。
query_start = 2 + 2 * N
results = []
# 各休憩プランについて判定を行います。
for j in range(M):
# L_j と R_j は 1-indexed で与えられます。
L = data[query_start + 2 * j]
R = data[query_start + 2 * j + 1]
# 区間 [L, R] 内に弁当不足のバスが 1 台も存在しない場合、
# pref[R] - pref[L-1] が 0 になります。
if pref[R] - pref[L-1] == 0:
results.append("Yes")
else:
results.append("No")
# 判定結果を改行区切りで一括出力します。
sys.stdout.write("\n".join(results) + "\n")
if __name__ == "__main__":
solve()
この解説は gemini-3-flash-thinking によって生成されました。