Python3で解く AtCoder Beginner Contest 172 C - Tsundoku
コンテストの最中は、探索するというか貪欲的に求めると勘違いしてしまった。heapqとdequeを駆使してごちゃごちゃやったが条件を詰められず。
けんちょんさんがこの問題は、しゃくとり法や累積和 +二分探索で考えたということをTweetされていたので、2つとも考えてみることにした。
ABC 172 お疲れ様でしたー!!!
— けんちょん 🍡( ´ ꒳ `🌸) (@drken1215) 2020年6月27日
C:A 側の個数を固定して、B 側で何個まで取れるか (しゃくとり法か、累積和+二分探索)
D:エラトステネスのふるいを用いた高速素因数分解で殴った
E:包除原理
F:XOR SUM!a + b = X, a ^ b = Y, a <= M を満たす最大の a を求める。a と M の一致桁数で場合分け。
概要
- の机に積んある本を読むのにかかる時間のリストがある。
- かのリストの左側からしか読めない。
- スタート時を例にするとを読んだら次はかが読める。
- のが若いほど上に積まれているということ
- 読んだ結果の時間の合計がを超えないようにする。
- 最大で何冊読める?
解くときに考えた内容
ダメパターン 1
いわゆる全探索的に行う。の制約が最大200000であり、O(MN)では間に合わない。
ダメパターン 2
読む時間が少ないものから読んでいけば、本の数は多くなるだろうと考えてGreedyに解こうとすると、複雑になりすぎてダメ。(きちんと実装しきればいけるのかもだが。。。)多分時間的にも無理でしょう(計算量把握できてない。。。)
しゃくとり法
- 都度どれだけ読んだかを計算するのは効率が悪いので、において、0冊を含めて、冊読んだら合計で何分なのかというのを計算したリストを用意する。(とする)
- はまでか、を超えるまで回す。
- はおしりから見ていって、 がを超えている間はをお尻から見続ける。
- がを超えなくなった時点ののindexとのindexを合計した値がその時点の目的である最大値となるので、maxvと比較して大きい方をmaxvとする。
- 引き続きを回し続けるが、を見る時のindexは直前まで見ていたところから、探索を続ける <- ここがポイント
この探索の仕方であれば、O(M+N)ということになる。そのイメージを自分なりに図示した。 つまり、最悪ケースは矢印がそれぞれ最後まで行ったとき。 がストップするところは、において、がを超えない、一番大きいbcumのindexである。がを超えなくなるまで、からしていき、そのindexを見つける実装をするということ。(と思っている。)
単調増加であることを利用して、を見る時のindexは直前まで見ていたところから、探索を続けるため、になり、ダメパターン1と比べて大きく計算量が減る
二分探索
考え方的に、こっちの方が簡単だということに気づいた。
- 都度どれだけ読んだかを計算するのは効率が悪いので、において、0冊を含めて、冊読んだら合計で何分なのかというのを計算したリストを用意する。(とする)
- はまでか、を超える手前まで回す。
- を満たす最大のindexであるを返す(だだし、0番目をリストに加えることになるのでj - 1が実際の本の冊数)
- 現状のmaxvと 比較してをmaxvとする。
を固定して、その時とれる最大ののindexをとる。こういうときに二分探索を使うと効率がいいということが勉強できた。累積和なので単調増加であり、二分探索を使う条件にも合っている。これは勉強になった。
コード
しゃくとり法
def main(): n, m, k = map(int, input().split()) a = [int(x) for x in input().split()] b = [int(x) for x in input().split()] acum = [0] * (n + 1) bcum = [0] * (m + 1) for i in range(n): acum[i + 1] = acum[i] + a[i] for i in range(m): bcum[i + 1] = bcum[i] + b[i] j = m maxv = 0 for i in range(n+1): if acum[i] > k: break while acum[i] + bcum[j] > k: j -= 1 maxv = max(maxv, i + j) print(maxv) if __name__ == '__main__': main()
二分探索
from bisect import bisect_right n, m, k = map(int, input().split()) a = [int(x) for x in input().split()] b = [int(x) for x in input().split()] maxv = 0 acum = [0] * (n + 1) bcum = [0] * (m + 1) for i in range(n): acum[i + 1] = acum[i] + a[i] for i in range(m): bcum[i + 1] = bcum[i] + b[i] for i in range(n + 1): if acum[i] > k: break maxv = max(maxv, i + bisect_right(bcum, k - acum[i]) - 1) print(maxv)