torus711 のアレ

主に競技プログラミングの問題について書きます.PC 以外だと数式が表示されないかもしれないです

AtCoder Beginner Contest 339, C : Perfect Bus

問題概要

 あるバスがあり,ある時点で非負の人数が乗っていた.その時点からのバスの乗降記録が与えられる.乗降記録は数列 $A = \langle A_1, A_2, \dots, A_n \rangle$ の形で与えられ,$A_i$ は $i$ 箇所目の停留所で乗客が $A_i$ 人増えた(負の場合はその絶対値分減った)ことを表す.
 乗降記録に矛盾しない(i.e., 乗客数が負にならない),記録終了時のバスの乗客数としてあり得る最小値はいくらか?

制約

  • $1 \leq n \leq 2 \times 10^5$
  • $-10^9 \leq A_i \leq 10^9$

解法

 何か基準が無いと難しいので,初期状態からの変化分について考えることにします.$A$ の添字を時刻だと捉えると,時刻 $i$ の乗降が終わった時点での乗客の数は,時刻 $i - 1$ での乗客の数に $A_i$ を足した値となります.初期状態の乗客数を $0$ と仮置きし,時刻 $i$ での乗客数を $P_i$ と書くことにすれば,$P$ は漸化式
\begin{equation*}
P_i = \begin{cases}
0 & \text{($i = 0)$} \\
P_{ i - 1 } + A_i & \text{(otherwise)}
\end{cases}
\end{equation*}
によって求まります(このような列は(競プロ文脈では?)「累積和」と呼ばれ,しばしば有用です).$P_i$ はある時刻での乗客数の初期状態からの変化量を表しています.もし $P$ の最小値 $\min P$ が負だったとすれば,初期の乗客数を $0$ と仮置きしたのは少なすぎたということになり,負にならないように調整する必要があります.最小限の調整幅はどれだけかというと,$P$ 全体が $-( \min P )$ だけ正方向にズレればよく,その場合の最終的な乗客数は $P_n - \min P$ となります.
 この解法の場合,累積和とその最小値の計算に $\Theta( n )$ 時間がかかります.そもそも入力に $\Theta( n )$ 時間かかるので,これでよいです.

実装について

 C++ では,累積和の計算に std::partial_sum が使えます.先頭に $0$ が欲しいので,

vector< int > p( 1 );

と用意しておいて std::back_inserter で挿入してあげるといい感じになります.
 Haskell では scanl が使えて,

scanl (+) 0 as

で終わりです.

コード (C++)

int main()
{
	IN( int, N );
	VLL A( N );
	cin >> A;

	VLL psum( 1 );
	partial_sum( ALL( A ), BI( psum ) ); 

	cout << psum.back() - *min_element( ALL( psum ) ) << endl;

	return 0;
}

コード (Haskell)

main = do
	getLine
	as <- readIntegers
	let
		psum = scanl (+) 0 as
		mi = minimum psum
	print $ last psum - mi