Port 53

明日のための技術メモ

累積和としゃくとり法

今日は、ABC172で累積和としゃくとり法の複合問題が出たので、
この2つのアルゴリズムを簡単にまとめる。

port53.hatenablog.com

目次

累積和

port53.hatenablog.com

実は累積和はABC152 C問題でも出ていたが、当時あまり良くわかっていなくて
いまひとつちゃんと説明できなかった。

累積和は、数列 $ A $ について、最初からi番目の和を求めたリストのこと。
$ A $ の累積和を $ S $ とした時、こんな感じで計算する。

$$ A = [1, 2, 3, 4, 5] $$

として

$$ \begin{eqnarray} &A_0& = 1 \\ &A_0& + A_1 = 1 + 2 =3 \\ &A_0& + A_1 + A_2 = 1 + 2 + 3 = 6 \\ &A_0& + A_1 + A_2 + A_3 = 1 + 2 + 3 + 4 = 10 \\ &A_0& + A_1 + A_2 + A_3 + A_4 = 1 + 2 + 3 + 4 + 5 = 15 \end{eqnarray} $$

というように、最初からひとつずつ項を増やして足していく。
その結果、 数列 $ A $ の累積和 $ S $ は、以下のようになる

$$ S = [1, 3, 6, 10, 15] $$

ライブラリとしては、itertools.accumulateや、numpy.cumsum(list)が使える。

しゃくとり法

しゃくとり法は、ある数列の指定された条件を満たす区間について、最大最小を出す・数え上げをする時に使える。
ここでの「区間」は、連続する部分列であり、半開区間 [a, b)として考える。

区間」の左端をleft,右端をrightとしたとき、以下の場合なら発動できる。

  • leftに対して、条件を満たすrightの最大値f(left)は広義単調増加関数
  • leftに対して、条件を満たすrightの最小値f(left)は広義単調増加関数

条件を満たしていることを確認しながら、rightとleftを1つずつずらして確認する。

今回、リストのインデックスと中身を同時に扱うため、enumerate関数を使ってループを回す。

ABC172 C. Tsundoku

f:id:saturn-glave:20200708235207p:plain

問題文

蓋を開けたら累積和としゃくとり法の複合だった。
累積和と二分探索でもできるらしいが、略。

考え方として、A,Bの本の山について、例えば山Aの1番目からn番目まで読んだら
トータル何分かかるかを事前に求めておく。
(全く読まない場合は0分なので、それも含める)
これは、累積和でささっと求められる。

次に、Aの山から読む本の冊数を固定し、その時Bの山からいくつ読むか
考える。この時Bから読む本の冊数はB全てから1冊ずつ減らし、
A, Bの読破時間合計が制限時間kに引っかからなくなったら、その時の冊数を答えとして記録する。
できるだけ多く本を読みたいので、全通り試して冊数の最大値を出す。
この部分は、しゃくとり法で実装した。

解説ACしたコード

# coding:utf-8
import itertools
n, m, k = map(int, input().split())
a = list(map(int, input().split()))
b = list(map(int, input().split()))
ans = 0
j = m

aa = [0] + list(itertools.accumulate(a))
bb = [0] + list(itertools.accumulate(b))
# print(aa)
# print(bb)


# しゃくとり法 / Aの山だけ読みつつ、そこにBからいくつ取り出すかで考える
for i, a_item in enumerate(aa):
    if a_item > k:
        break
    while a_item + bb[j] > k:
        j -= 1
    ans = max(i + j, ans)
print(ans)

参考

累積和を何も考えずに書けるようにする! - Qiita

qiita.com