結果

問題 No.3670 Fast Knapsack
コンテスト
ユーザー harurun
提出日時 2026-09-01 22:53:30
言語 PyPy3
(7.3.23)
コンパイル:
pypy3 -mpy_compile _filename_
実行:
pypy3 _filename_
結果
AC  
実行時間 496 ms / 2,500 ms
+ 66µs
コード長 8,706 bytes
記録
記録タグの例:
初AC ショートコード 純ショートコード 純主流ショートコード 最速実行時間
コンパイル時間 542 ms
コンパイル使用メモリ 96,212 KB
実行使用メモリ 133,392 KB
最終ジャッジ日時 2026-09-04 23:03:36
合計ジャッジ時間 6,038 ms
ジャッジサーバーID
(参考情報)
judge1_0 / judge4_0
このコードへのチャレンジ
(要ログイン)
ファイルパターン 結果
sample AC * 1
other AC * 25
権限があれば一括ダウンロードができます

ソースコード

diff #
raw source code

import sys
from math import gcd
from bisect import bisect_right

import pypyjit

pypyjit.set_param(
    "threshold=1,"
    "function_threshold=1,"
    "trace_eagerness=1,"
    "decay=0"
)


# 63 bit / word
#
# 64 bit にすると 2^63 以上の値が現れて PyPy の多倍長整数に
# 昇格する可能性がある。
W = 63

# r bit 左シフトするとき、
# 下側 word に残る部分だけを先に mask する。
#
# これにより
#
#     (x & LOW_MASK[r]) << r < 2^63
#
# が常に成立する。
LOW_MASK = [0] * W

r = 1
while r < W:
    LOW_MASK[r] = (1 << (W - r)) - 1
    r += 1


def solve():
    data = list(map(int, sys.stdin.buffer.read().split()))

    pos = 0
    T = data[pos]
    pos += 1

    out = [""] * T

    tc = 0
    while tc < T:
        n = data[pos]
        S0 = data[pos + 1]
        pos += 2

        end = pos + n

        freq = {}
        g = 0
        total = 0

        while pos < end:
            a = data[pos]
            pos += 1

            if a <= S0:
                total += a
                g = gcd(g, a)
                freq[a] = freq.get(a, 0) + 1

        if not freq:
            out[tc] = "0"
            tc += 1
            continue

        if total <= S0:
            out[tc] = str(total)
            tc += 1
            continue

        # 全ての部分和は g の倍数なので問題を縮約する。
        S = S0 // g

        pairs = []

        capped_sum = 0
        min_a = S + 1
        max_a = 0

        for aa, c in freq.items():
            a = aa // g

            # これを超える個数は、どの feasible subset にも入らない。
            lim = S // a
            if c > lim:
                c = lim

            pairs.append((a, c))

            capped_sum += a * c

            if a < min_a:
                min_a = a
            if a > max_a:
                max_a = a

        # 使用可能個数まで削った後、全部入る。
        if capped_sum <= S:
            out[tc] = str(capped_sum * g)
            tc += 1
            continue

        # ------------------------------------------------------------
        # 値域が狭い場合の高速処理
        #
        # k = floor(S / max_a)
        #
        # k 個なら最大値だけを k 個選んでも S 以下。
        #
        # 一方、
        #
        #     (k + 1) * min_a > S
        #
        # なら k+1 個は絶対に選べない。
        #
        # よって「大きい順に k 個」が最適。
        # ------------------------------------------------------------

        k = S // max_a

        if (k + 1) * min_a > S:
            pairs.sort(reverse=True)

            need = k
            ans = 0

            i = 0
            m = len(pairs)

            while need and i < m:
                a, c = pairs[i]

                if c < need:
                    take = c
                else:
                    take = need

                ans += a * take
                need -= take

                i += 1

            out[tc] = str(ans * g)
            tc += 1
            continue

        # ------------------------------------------------------------
        # bounded subset sum
        #
        # c 個の同じ値 a を
        #
        # a, 2a, 4a, ...
        #
        # に二進分解する。
        # ------------------------------------------------------------

        chunks = []

        for a, c in pairs:
            p = 1

            while c:
                if p < c:
                    take = p
                else:
                    take = c

                chunks.append(a * take)

                c -= take
                p <<= 1

        chunks.sort()

        # 二進分解後にも同じ判定を入れる。
        min_c = chunks[0]
        max_c = chunks[-1]

        k = S // max_c

        if (k + 1) * min_c > S:
            out[tc] = str(sum(chunks[-k:]) * g)
            tc += 1
            continue

        # ------------------------------------------------------------
        # S/2 より大きい chunk は、解に高々 1 個しか入らない。
        #
        # したがってこれらは bitset に投入しない。
        # ------------------------------------------------------------

        split = bisect_right(chunks, S >> 1)

        top_word = S // W

        # +2 にして carry の境界判定を削除する。
        bits = [0] * (top_word + 2)
        bits[0] = 1

        # 処理済み chunk の総和。
        # reachable bit の上限として使う。
        cap = 0

        target_word = top_word
        target_bit = S - target_word * W

        i = 0

        # ------------------------------------------------------------
        # 固定長 bitset DP
        # ------------------------------------------------------------

        while i < split:
            a = chunks[i]

            # shift 元として見る必要がある最大 bit。
            #
            # x > S-a なら x+a > S なので無視できる。
            lim = cap

            sa = S - a
            if lim > sa:
                lim = sa

            src = lim // W

            q = a // W
            r = a - q * W

            if r == 0:
                while src >= 0:
                    x = bits[src]

                    if x:
                        bits[src + q] |= x

                    src -= 1

            else:
                low_mask = LOW_MASK[r]
                rr = W - r

                while src >= 0:
                    x = bits[src]

                    if x:
                        dst = src + q

                        # 下側
                        #
                        # shift 前に mask することで
                        # 多倍長整数への昇格を防ぐ。
                        bits[dst] |= (x & low_mask) << r

                        # 上側 carry
                        bits[dst + 1] |= x >> rr

                    src -= 1

            cap += a

            if cap > S:
                cap = S

            # S が作れた時点で終了。
            if (bits[target_word] >> target_bit) & 1:
                out[tc] = str(S * g)
                break

            i += 1

        else:
            # --------------------------------------------------------
            # 各 word より前にある非零 word を記録する。
            #
            # S/2 より大きい chunk に対する
            #
            #     max reachable <= S-a
            #
            # を O(1) 近くで求めるため。
            # --------------------------------------------------------

            prev = [-1] * (top_word + 1)

            last = -1
            j = 0

            while j <= top_word:
                if bits[j]:
                    last = j

                prev[j] = last
                j += 1

            # small chunks のみで作れる最大値。
            wi = top_word
            rb = S - wi * W

            x = bits[wi]

            if rb != W - 1:
                x &= (1 << (rb + 1)) - 1

            if x:
                best = wi * W + x.bit_length() - 1
            else:
                wi -= 1

                if wi >= 0:
                    wi = prev[wi]

                if wi >= 0:
                    best = wi * W + bits[wi].bit_length() - 1
                else:
                    best = 0

            # --------------------------------------------------------
            # 大きい chunk を 1 個だけ加える。
            # --------------------------------------------------------

            i = split
            m = len(chunks)

            while i < m:
                a = chunks[i]
                limit = S - a

                wi = limit // W
                rb = limit - wi * W

                x = bits[wi]

                if rb != W - 1:
                    x &= (1 << (rb + 1)) - 1

                if x:
                    small_best = wi * W + x.bit_length() - 1

                else:
                    wi -= 1

                    if wi >= 0:
                        wi = prev[wi]

                    if wi >= 0:
                        small_best = (
                            wi * W
                            + bits[wi].bit_length()
                            - 1
                        )
                    else:
                        small_best = 0

                v = a + small_best

                if v > best:
                    best = v

                    if best == S:
                        break

                i += 1

            out[tc] = str(best * g)

        tc += 1

    sys.stdout.write("\n".join(out))


if __name__ == "__main__":
    solve()
0