【競プロ典型90問】「051 - Typical Shop(★5)」解法
Free-PhotosによるPixabayからの画像

問題

051 - Typical Shop(★5)

ポイント

競プロに親しみがある方だとこの問題を見たときに
ナップサック問題と似ている→動的計画法で解ける!と思われるかもしれません。

ただ、残念ながらこの問題を動的計画法で解くことはできないのです・・・。
理由は値段の合計Pと、各商品の値段Aiの上限が大きい(10の18乗)ことにあります。

動的計画法を行うにははじめに0~P、0~Aiまでの配列を用意しなければならないのですが、
リストのデータ型では10の18乗までのインデックスには対応していません。

・・・かと言って全部の商品の選び方を検証しようとすれば最高で2の40乗の計算を
しなければならず、TLEとなってしまいます。

ではどうするか。
ここでは半分全列挙という方法を利用します。※正式な呼称ではないかもしれません・・・。

次の手順で解法を求めていきます。
 ①商品を2つのグループに分ける:グループ1とグループ2とする
 ②グループ1と2それぞれで商品の組み合わせをすべて求める。
 ③グループ1からある組み合わせを選び、その値段の合計をc円とする
 ④グループ2からP-c円以下となる組み合わせを調べる

ポイントは①と②です。それぞれのグループの商品の選び方を調べるのには最高でも
2 x 2の20乗しかかかりません。そのためTLEを回避できます。

以上を踏まえ、実装したコードはこちらです。
※④でP-i円以下の組み合わせを調べる際、二分探索という方法を利用しています。
 二分探索の詳細は下記記事が参考になります。
 AtCoder灰・茶・緑色の方必見!二分探索を絶対にバグらせないで書く方法

コード

def main():
    import sys
    import bisect
    from itertools import combinations
    sys.setrecursionlimit(10 ** 9)
    input = sys.stdin.readline

    N, K, P = map(int, input().split())
    A = list(map(int, input().split()))

    group1, group2 = A[:N//2], A[N//2:]
    group1_price = [[] for _ in range(K + 1)]  # 0~K個選んだときの価格を格納

    for i in range(K+1):
        for c in combinations(group1, i):  # group1からi個選んだときの組み合わせ
            group1_price[i].append(sum(c))
        group1_price[i].sort()  # 価格を昇順にソートする→後で二分探索を行うため

    ans = 0
    for i in range(K+1):
        for c in combinations(group2, i):  # group2からi個選んだときの組み合わせ
            # group1からK-i個選んだときの組み合わせの内、金額がP-sum(c)以下の数
            # 二分探索を行う
            index = bisect.bisect(group1_price[K-i], (P - sum(c)))
            ans += index
    print(ans)


if __name__ == '__main__':
    main()
おすすめの記事