D - Money in Hand Editorial by cirno3153

bit演算を使った実装を更に高速化

こちらのユーザー解説を更に高速化します。

今、 \(A_i\) 円硬貨が \(B_i\) 枚あるとします。 この時、これを幾つかの集合に分けて、この組み合わせで \(0\) 枚から \(B_i\) 枚を表すことを考えます。

\(2^n \leq B_i < 2^{n+1}\) となる \(n\) を取ってきたとき、 \(1, 2, \ldots, 2^{n-2}, 2^{n-1}, B_i - 2^n+1\) 枚の集合を作ると、 \(0\) 枚から \(B_i\) 枚まで全てを表すことができます。

\(1, \ldots, 2^{n-1}\) 枚の集合は2進数なので、作りたい枚数の対応する桁と同じ部分を持ってくることで \(0\) 枚から \(2^n-1\) 枚までを作ることができます。 \(2^n \leq x \leq B_i\) の範囲で \(x\) 枚を作りたい時は、まず \(B_i - 2^n+1\) 枚を選べばよく、 \(B_i - 2^n+1 \leq 2^n\) よりこの範囲の全ての枚数を表すことができます。

上の方法を用いることで、結局 \(A_i, 2A_i, \ldots, 2^{n-1}A_i, (B_i - 2^n+1)A_i\) 円が書かれた硬貨を \(1\) 枚ずつ持っている問題に帰着され、これは部分和問題として解くことができます。

計算量は \(O(\frac{X \sum_{i=1}^{N} \log B_i}{\rm wordsize})=O(\frac{NX \log \frac{X}{N}}{\rm wordsize})\) です。 \(\log \frac{X}{N} = o({\rm wordsize})\) より、これは \(o(NX)\) となります。

N, X = map(int, input().split())
s = 1 << X
for _ in range(N):
  a, b = map(int, input().split())
  use_set = 1 # 一度に纏めて硬貨aをuse_set枚使う
  while b != 0:
    use_set = min(use_set, b) # 1, 2, 4, ..., 2^{n-1}, 足すとbになる残りの枚数、と遷移
    s |= s >> a * use_set
    b -= use_set
    use_set <<= 1
print("Yes" if s & 1 else "No")

posted:
last update: