🔍

[鉄則B14] Another Subset Sum

2024/05/10に公開

https://atcoder.jp/contests/tessoku-book/tasks/tessoku_book_cm

半分全列挙の応用問題。

問題を要約する

  • N 枚から 0 枚以上を選んだとき合計が K になることはあるか?

例1で問題を理解する

0 1 2 3 4 5
5 1 18 7 2 9

5, 18, 7 を選んだとき K(30) になる。

解き方

  1. A を半分にする → X, Y
  2. X, Y 内で総当たり足し算を行う → P, Q
  3. P + Q = K になるか調べる

最後は Q = K - P と考えて Q の中に K - P があるかを探す。最初に半分に分けるのは適当でいい。X, Y のサイズは異なっていてもいい。

はまりポイント

0 枚以上のカードを選ぶ。つまり選ばないのときもある。

例1のシミュレーション

A の、

0 1 2 3 4 5
A 5 1 18 7 2 9

を半分にして、

半分
X 5 1 18
Y 7 2 9

総当たりで足し算し、

結果
P 0 5 1 18 6 23 19 24
Q 0 2 7 9 9 11 16 18

30 - 0 の 30 が Q に含まれるか探す。線形探索だと遅いので二分探索を行う。しかし 30 はなかったので次に進む。これを繰り返すと 23 のときに 30 - 23 で 7 が Q に見つかる。

全探索 (TLE)

N, K = gets.split.collect(&:to_i)  # => [6, 30]
A = gets.split.collect(&:to_i)     # => [5, 1, 18, 7, 2, 9]
ans = (0..N).any? do |max|
  A.combination(max).any? do |e|
    e.sum == K
  end
end
puts ans ? "Yes" : "No"

半分全列挙 (AC)

124 ms
N, K = gets.split.collect(&:to_i)                   # => [6, 30]
A = gets.split.collect(&:to_i)                      # => [5, 1, 18, 7, 2, 9]
X = A.take(N / 2)                                   # => [5, 1, 18]
Y = A.drop(N / 2)                                   # => [7, 2, 9]
P = (0..X.size).flat_map { |max| X.combination(max).collect(&:sum) }
Q = (0..Y.size).flat_map { |max| Y.combination(max).collect(&:sum) }.sort
P                                                   # => [0, 5, 1, 18, 6, 23, 19, 24]
Q                                                   # => [0, 2, 7, 9, 9, 11, 16, 18]
ans = P.any? { |e| Q.bsearch { |q| (K - e) - q } }  # => true
puts ans ? "Yes" : "No"

bsearch は find の高速版としたいので find-any モードにする。

参考

Discussion