Boot camp for Beginners hard 005【D – Powerful Discount Tickets】をpython解説

Boot camp for Beginners hard

Atcoder Problems という競プロを勉強する上でのコンテンツがあります。URL

今回は、hard100問の内、5問目を解説していきたいと思います。

問題名 【D – Powerful Discount Tickets】

問題

入力

N : 商品の数
M : 割引券の枚数
a[i] : i番目の商品の値段

考察

割引券の枚数分だけ、任意の商品の値段を半額にすることができます。なのでM回の試行のうち、その都度値段が最大の商品を半額にすれば答えが出ます(貪欲法)

しかし、リストから最大値を取る作業は、最大でO(N)の計算量がかかるため、M回その作業をしてしまうと計算量が間に合いません。 O(M*N) > 10の7乗

ではどうするか?

事前にソートしておきましょう。
そうすればリストの末尾が最大値となるため、一回の試行につき、indexを指定すればO(1)でデータを取ってこれます。

その後、半額した値を戻すのですが、せっかくソートされているので、リストに戻す際も、ソート順になるように戻したいです。(そうしないと、毎回ソートすることになり、TLE)
なので、整列された数列に対し、その列を崩さず、値を挿入することができる bisect_insertを使います。

これで、全体の計算量がO(M*log(N))となり、間に合います

コード

import sys
def LI(): return list(map(int,sys.stdin.readline().rstrip().split()))
def II(): return int(sys.stdin.readline())
def MI(): return map(int,sys.stdin.readline().rstrip().split())
def S(): return sys.stdin.readline().rstrip()
readline = sys.stdin.readline
sys.setrecursionlimit(10 ** 6)

import bisect
N,M = MI()
A = LI()


"""
sortしてから、一番大きいのをpop,bisectで挿入をくりかえす
"""

A.sort()
for i in range(M):
    tmp = A.pop()
    new_tmp = tmp//2
    bisect.insort(A,new_tmp)
#     print(A)
# print(A)
print(sum(A))

もっと考える

上記の方法でもACにはなるのですが、実はもっと早く実行できる方法があります。それは、優先度付きキューを使用することです。

優先度付きキューとは、ある数列において、従来のリストよりも計算量を抑えながら、数列の最小値のpop,新しい値のpushができる構造を持つ配列です。(詳しくは、pythonライブラリのheapのところに書いてあると思います)

なので、今回はそれを利用して、問題を解きます!優先度付きキューはライブラリもあるのですが、僕は、うにだよ様が以前解説の際に使用していた優先度付きキューを使用させていただいてます。

import sys
def LI(): return list(map(int,sys.stdin.readline().rstrip().split()))
def II(): return int(sys.stdin.readline())
def MI(): return map(int,sys.stdin.readline().rstrip().split())
def S(): return sys.stdin.readline().rstrip()
readline = sys.stdin.readline
sys.setrecursionlimit(10 ** 6)

import bisect
N,M = MI()
A = LI()


"""
貪欲法
現段階での最大値を求める
→priority queueを使う
"""

class PriorityQueue:
    def __init__(self, a=None):
        import heapq
        self.heapq = heapq
        self.__container = []
        if a:
            self.__container = a[:]
        self.heapq.heapify(self.__container)

    @property
    def is_empty(self):
        return not self.__container

    def pop(self):
        #最小値を取り出す
        return self.heapq.heappop(self.__container)
    
        
    def push(self, x):
        #xをheapにpush
        self.heapq.heappush(self.__container, x)

    def sum(self):
        #合計値を出力
        return sum(self.__container)

    def __len__(self):
        return len(self.__container)

    def __str__(self):
        return str(sorted(self.__container))

    def __repr__(self):
        return self.__str__()

B = []
for i in range(N):
    B.append(A[i]*-1)
    #優先度付きキューは最小値をpopするので、正負を入れ替えている

    
PQ = PriorityQueue(B)

for m in range(M):
    a = PQ.pop()
    x = (a*-1)//2
    #取り出した値は正負を入れ替得られているので、元に戻し、//2する
    PQ.push(x*-1)
    #再度、-1をかけてpush
    
print(PQ.sum()*-1)

まとめ

今後、計算量がシビアな問題が多発すると思うので、今のうちから計算量を落とすテクニックや解法を覚えておきましょう!

前の問題【B – Between a and b …】
次の問題【C – Dubious Document 2】

コメント

タイトルとURLをコピーしました