🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Q&A

解決済

1回答

788閲覧

[python3][アルゴリズム] DPの部分和で詰まってしまった

退会済みユーザー

退会済みユーザー

総合スコア0

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

0グッド

0クリップ

投稿2019/10/27 08:26

編集2019/10/27 08:47

今DPを勉強中で、AtCoderというサイトでいろいろ問題を解いています

この問題で詰まりました
http://atcoder.jp/contests/abc047/tasks/abc047_a

これ自体は簡単なのですが
子供の数がX人、パックの数がN個になった時にどうなるかと考えてみて、
X >= 3のときにアルゴリズムはわかるが実際にどう書いたら良いか分からなくなりました。

例えばX = 3なら、パックの部分和のキャンディが全部のキャンディの1/3になる組み合わせがあれば
その組み合わせをパックのリストから除外して残ったリストからまたパックの同じように部分和を考えればいいと思います。

X > 3についてもloopを使えばできると思うのですが、具体的にどう書けば良いのでしょうか

###問題自体の解答はこれです
dp[i+1][j] : i番目までのパックからいくつか選んで中のキャンディの総和が全部のキャンディの半分(この問題では子供が二人なので)になるようにすることが可能かどうかの論理知。

dp[i][j]がTrueならdp[i+1][j]もTrue
dp[i][j]がFalseのとき、(j >= packs[i]の場合のみ)dp[i][j-packs[i]]をみてTrueならdp[i+1][j]もTrue

参考: https://qiita.com/drken/items/a5e6fe22863b7992efdb#%E5%95%8F%E9%A1%8C-3%E9%83%A8%E5%88%86%E5%92%8C%E5%95%8F%E9%A1%8C

python3

1packs = [int(candies) for candies in input().split()] 2N = int(len(packs)) 3X = 2 4if sum(packs) % X == 0: 5 A = int(sum(packs) / X) # 部分和がAになれば良い 6else: 7 print('No') 8 exit() 9 10dp = [[False] * (A + 1)] * (N + 1) 11dp[0][0] = True 12 13for i in range(N): 14 for j in range(A+1): 15 if dp[i][j]: 16 dp[i+1][j] = dp[i][j] 17 elif j >= packs[i]: 18 dp[i+1][j] = dp[i][j - packs[i]] 19 20if dp[N][A]: 21 ans = "Yes" 22else: 23 ans = "No" 24 25print(ans) 26

追記

i, j に加えて現在のリストの情報kも必要ですよね

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

例えばX = 3なら、パックの部分和のキャンディが全部のキャンディの1/3になる組み合わせがあれば
その組み合わせをパックのリストから除外して残ったリストからまたパックの同じように部分和を考えればいいと思います。

この考えをベースにする場合、現在使われてるパックの状態をdp配列に持たせる必要があります。
例えば整数kの二進数表記を使っているパック集合を表すとすれば、もはや何番目のパックまで調べたかを表すiは不要になりますし、現在使われているキャンディの総和jも不要になります。
(補足すると、同じパックを重複して使うようなことがないように気を付けさえすれば元の問題でもiは不要で、使われてるパックが分かれば現在使われてるキャンディの総和を計算することができるので、i,jともに不要になります。)

あとは dp[k] を何を表す配列とするかですが、

sum(k) := 集合kに含まれるキャンディの総和 Qk := sum(k) // A #あまり切り捨て Rk := sum(k) % A

としたとき

dp[k] := 集合kのパックを全て使って、Qk人にA個ずつ配り、Rk個余るような組み合わせが存在するならTrue、そうでなければFalse

とすることで

i := k に含まれないパック k|i := k に i を追加した集合 C[i] := パックiのキャンディの個数 dp[k] := (Qk == Qk|i or Rk + C[i] == A)がいずれかのiについてTrue

と遷移できます。

Python

1X = int(input()) 2packs = [int(candies) for candies in input().split()] 3N = int(len(packs)) 4if sum(packs) % X == 0: 5 A = int(sum(packs) / X) # 部分和がAになれば良い 6else: 7 print('No') 8 exit() 9#集合ごとのキャンディの合計 10packSum = [sum([packs[x] for x in range(N) if i & (1 << x) != 0]) for i in range(1 << N)] 11dp = [False] * (1 << N) 12dp[0] = True #どのパックも使わないとき条件を満たす 13for i in range(len(dp)): #現在使われてるパックの集合 14 if not dp[i]: 15 continue 16 for j in range(N): #追加するパックの候補 17 if i & (1 << j) == 0: #すでに使われていないか調べる 18 dp[i | (1 << j)] = dp[i | (1 << j)] or packSum[i] // A == packSum[i | (1 << j)] // A or packSum[i] % A + packs[j] == A 19 20 21if dp[-1]: 22 ans = "Yes" 23else: 24 ans = "No" 25 26print(ans)

普段Python書かないので自信がないですが、こんな感じのコードになります。

投稿2019/10/31 09:22

yudedako67

総合スコア2047

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問