1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

AtCoder アルゴリズムと数学 009 - Brute Force 2 解答例(Pythonでのbit全探索 / 動的計画法のシンプルなコード例)

Posted at

Supershipの名畑です。季節の変わり目が訪れるたび、TVアニメ てーきゅうの10期は放送されないのだろうかと期待してしまいます。

はじめに

コードを書きたい気分のときにはよくAtCoderを利用させてもらっています。

AtCoderでは、競技プログラミングの世界で有名なE869120さんが執筆されました書籍「問題解決のための「アルゴリズム×数学」が基礎からしっかり身につく本」の演習問題集が公開されていまして、それをたまに解いています。

そのうちの1問について、解法を残しておきたくなりましたので、残します。
bit全探索動的計画法のシンプルなサンプルコードとして良いなと思ったのです。

009 - Brute Force 2

問題文は009 - Brute Force 2にございます。

正の整数が書かれたN枚のカードの中から、合計がSになる組み合わせがあるかをYes or Noで答えるという問題です。

1≦N≦20について解けると部分点として500点が、N≦60までを解けると1000点です。

bit全探索による500点のコード

たとえばN3で、各カードをA0A1A2とします。

このときとりうるカードの組み合わせは

  • A0
  • A1
  • A1 + A0
  • A2
  • A2 + A0
  • A2 + A1
  • A2 + A1 + A0

の7パターンです。(問題文においてS1以上のため、カードを選ばないことはない)

2進数で記すなら

  • 001
  • 010
  • 011
  • 100
  • 101
  • 110
  • 111

と書けます。左から順にそれぞれの桁がA2A1A0の有無を示しているとします。

コードにすると下記です。2進数各桁の取得は右シフトしつつ1との論理積をとっています。1との論理積をとれば下1桁の値になります。

N, S = map(int, input().split())
A = list(map(int, input().split()))
for i in range(1, 2 ** N):
    total = 0
    for j in range(N):
        total += A[j] * ((i >> j) & 1)  # 2進数にした各桁が1のときだけAを足す
    if total == S:
        print('Yes')
        exit()
print('No')

計算量がNの2乗に比例するため、Nが大きくなると1000msecを超えてTLE(Time Limit Exceeded)です。

動的計画法(DP)による1000点のコード

たとえばN2で、各カードをA0A1とします。
そしてA02A13だとします。

1枚目のカードとしてA0を選んだとき、その時点での合計値は2です。選ばなければ0です。
つまり、A0だけしかカードがなかった場合、02がとりうる数です。

さらに、A1を選ぶか選ばないかまで考えると

  • 0 + 0 = 0
  • 0 + 3 = 3
  • 2 + 0 = 2
  • 2 + 3 = 5

この4パターンです。つまり、A0A1の2枚のカードしかなかった場合、0235の4つがとりうる値となります。
Sがこの中にあればYes、なければNoという考え方です。

これをコードで示すとどうなるでしょう。

N, S = map(int, input().split())
A = list(map(int, input().split()))
dp = [False] * (S + 1)  # 0〜Sまでの値をとりうるかを格納する配列
dp[0] = True  # 0のみTrueにしておく
for i in range(N):  # 0枚目から順番にカードを見ていく
    for j in reversed(range(0, S)):  # dpの値が途中で変わらないようにreversedしている
        if dp[j] and j + A[i] <= S:
            dp[j + A[i]] = True
if dp[S]:  # Sをとりうるか判定する
    print('Yes')
else:
    print('No')

計算量はS × Nに比例するため、問題におけるどのテストケースでも50msec以内で終わります。

最後に

考え方次第でこれだけ計算量が変わるのって、本当に面白い。

宣伝

SupershipのQiita Organizationを合わせてご覧いただけますと嬉しいです。他のメンバーの記事も多数あります。

Supershipではプロダクト開発やサービス開発に関わる方を絶賛募集しております。
興味がある方はSupership株式会社 採用サイトよりご確認ください。

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?