アールグレー特売所

ゲームやら競プロやらのメモ書き

AISing Programming Contest 2019 参戦記 (D問題)

AISingコン反省会後編です。A-C問題はこちら。

AISing Programming Contest 2019 参戦記 (A-C問題) - アールグレー特売所

 

D問題 (Nearest Card Game)

from math import ceil
N, Q = map(int, input().split())
A = [int(a) for a in input().split()]
AltTake = [0 for a in range(N)] #i番目から交互に取っていくときの高橋君の点数
GetAll = [0 for a in range(N)] #i番目以上を総取りしたときの点数
AltTake[0], AltTake[1] = A[0], A[1]
GetAll[-1] = A[-1]
BlockList = [0 for i in range(ceil(N/2))] #i番目以上を総取りするために必要なxの最小値
maxBlock = ceil(N/2) #最大でceil(N/2)枚しか総取りすることはできません
 
for i in range(2, N):
    AltTake[i] = A[i] + AltTake[i-2]
for i in reversed(range(N-1)):
    GetAll[i] = GetAll[i+1] + A[i]
for i in range(ceil(N/2) - 1):
        BlockList[-i-1] = (A[-i-2] + A[-2*i - 3]) // 2 + 1
 
def BSearch(x, L): #二分探索
    left, right = 0, len(L)
    while right - left > 1:
        mid = (right + left)//2
        if L[mid] > x:
            right = mid
        else:
            left = mid
    return left
 
for _ in range(Q):
    block = maxBlock - BSearch(int(input()), BlockList) #上からblock枚総取りできる
    if block == maxBlock: print(GetAll[-block])
    else: print(GetAll[-block] + AltTake[N - block * 2 -1])

ある条件に従ってカードを交互に取っていくゲームでした。少し自分で実験してみると、高橋君は大きいものi枚総取りして、青木君と取りたいカードが競合した後は残りのN - 2i枚を大きい方から交互に取っていくことがわかります。そこで、事前計算として、上i枚をとった時の点数(GetAll)、N-2i枚目から交互に取った時の高橋君の点数(AltTake)、上i枚とるために必要なxの最小値(BlockList)を求めておきます。こうすることで、xが与えられたときにBlockListを二分探索して上から何枚総取りできるかを出せば、O(1)で高橋君の点数を求めることができます。二分探索の計算量がO(logN)、事前計算の計算量がO(N + N + (N / 2))なので、全体ではO(2.5N + NlogN)でPython3でも制限時間内に解ききることができます。提出時には総取りの計算にO(N)掛けていますが、ceil(N/2)しか取れないことを考えると実はうえからceil(N/2)枚まで考えればよくて、全体の計算量を(2N + NlogN)にできます。