C - 旅行の計画 / Travel Plan 解説 by admin
Gemini 3.8 Flash (High)概要
\(N\) 日間の旅行から連続する区間(または \(0\) 日間)を取りやめ、旅行にかかる費用の合計をちょうど \(K\) 円にする方法が何通りあるかを求める問題です。 「取りやめる費用の合計」に着目し、条件を整理した上で尺取り法を用いることで \(O(N)\) で解くことができます。
考察
1. 問題の言い換え
旅行全体の総費用を \(S = \sum_{i=1}^N A_i\) とします。 残る費用をちょうど \(K\) 円にするということは、取りやめる費用の合計をちょうど \(T = S - K\) 円にすることと同値です。
取りやめる費用の選択肢は次の2通りです。 1. 1日も取りやめない場合: 取りやめる費用は \(0\) 円 2. 区間 \([l, r]\) を取りやめる場合: 取りやめる費用は \(\sum_{i=l}^r A_i\) 円
制約より各日の費用は \(A_i \ge 1\) なので、1日以上取りやめる場合の費用は必ず \(1\) 以上になります。この性質から、\(T\) の値によって場合分けができます。
- \(T < 0\) のとき:
取りやめる費用を負にすることはできないため、達成不可能です。答えは
0通りとなります。 - \(T = 0\) のとき:
取りやめる費用を \(0\) にする必要があるため、「1日も取りやめない」という \(1\) 通りのみとなります(区間を取りやめると \(A_i \ge 1\) より必ず \(1\) 以上減ってしまいます)。答えは
1通りです。 - \(T > 0\) のとき: 「1日も取りやめない」選択肢は費用が \(0\) なので除外されます。したがって、連続部分列の和がちょうど \(T\) となる区間 \([l, r]\) の個数を求めればよいことになります。
2. 素朴なアプローチと高速化
すべての区間 \([l, r]\) \((1 \le l \le r \le N)\) について総和を計算する方法では、区間の数が \(\frac{N(N+1)}{2}\) 通りあるため \(O(N^2)\) の計算量がかかります。本問の制約は \(N \le 10^6\) であるため、実行時間制限に間に合いません(TLE)。
ここで、すべての要素が正数(\(A_i > 0\))であることに着目します。 区間の右端を右に伸ばすと和は単調に増加し、左端を右に縮めると和は単調に減少します。この単調性を利用することで、尺取り法(Two Pointers) を用いて \(O(N)\) で探索できます。
アルゴリズム
尺取り法を用いて、連続部分列の和が \(T\) となる区間を数え上げます。
- 全体の合計 \(S\) を計算し、\(T = S - K\) を求める。
- \(T < 0\) なら
0を、\(T = 0\) なら1を出力して終了する。 - 区間の左端
left、右端right、現在の区間和cur、条件を満たす区間数ansを用意する。 rightを \(0\) から \(N-1\) まで順に進めながら、以下を行う:curに \(A[\text{right}]\) を加える。cur > Tである間、curから \(A[\text{left}]\) を引き、leftを \(1\) 進める。cur == Tとなった場合、和が \(T\) となる区間が見つかったのでansを \(1\) 増やす。
- 最終的な
ansを出力する。
計算量
- 時間計算量: \(O(N)\)
rightは \(0\) から \(N-1\) まで \(N\) 回進みます。また、leftも高々 \(N\) 回しか進まないため、ループ内の処理は全体で高々 \(2N\) 回しか実行されません。よって \(O(N)\) となり、十分に間に合います。 - 空間計算量: \(O(N)\) 配列 \(A\) を保持するために \(O(N)\) のメモリを使用します。
実装のポイント
\(N \le 10^6\) と入力サイズが大きいため、Python では
sys.stdin.readを用いて一括で入力を取得することで高速化を図っています。\(A_i \ge 1\) であるため、区間の総和が \(T\) になるような左端
leftは各rightに対して高々 \(1\) つしか存在しません。そのため、cur == Tの判定でそのままans += 1とカウントして問題ありません。ソースコード
import sys
def main():
input = sys.stdin.read
data = input().split()
if not data:
return
N = int(data[0])
K = int(data[1])
A = [int(x) for x in data[2:]]
S = sum(A)
T = S - K
if T < 0:
print(0)
return
if T == 0:
print(1)
return
ans = 0
cur = 0
left = 0
for right in range(N):
cur += A[right]
while cur > T:
cur -= A[left]
left += 1
if cur == T:
ans += 1
print(ans)
if __name__ == "__main__":
main()
この解説は gemini-3.8-flash-high によって生成されました。