Official

D - Raise Minimum Editorial by harurun4635


観察

配列 \(A = (1, 2, 3)\) が与えられた時 、\(K\) 回操作したときの \(ans\) はどうなるでしょうか?( \(ans\) が達成される \(A\) の一例も挙げますが、一例であることに注意してください)

  • \(K = 1\) では \(A = (2, 2, 3)\)\(ans = 2\)
  • \(K = 2\) では \(A = (3, 2, 3)\)\(ans = 2\)
  • \(K = 3\) では \(A = (3, 4, 3)\)\(ans = 3\)
  • \(K = 4\) では \(A = (4, 4, 3)\)\(ans = 3\)
  • \(K = 5\) では \(A = (4, 4, 6)\)\(ans = 4\)

このように \(K\) に対して \(ans\) が「単調増加」であるような性質があります。これを「解が単調」などと呼ぶことがあります。

解の二分探索

「解が単調」である問題では、しばしば 解の二分探索 が有用です。 \(ans\) を直接求めるのではなく、「 \(ans\)\(X\) 以上にすることが可能ですか?」という質問で解を二分探索することで、解を特定するというテクニックです。

これは多くの問題において、いわゆる「 \(ans\)\(X\) 以上にすることが可能ですか?」などの 判定問題 のほうが解くほうが(直接元の問題を解くより)簡単であることが多いことを利用しています。

判定問題を解く

今回の問題で解くべき判定問題は

「( \(K\) 回の操作で)\(ans\)\(X\) 以上にできるか?」

です。これは

\(ans\)\(X\) 以上にするのに、必要な操作回数は \(K\) 回以下か?」

と同じことですし、

「すべての要素を \(X\) 以上にするのに、必要な操作回数は \(K\) 回以下か?」

と同じことです。


最後の問題であれば簡単に解けます。すべての \(i ~(1 \le i \le N)\) について

  • \(A_i \ge X\) のとき、 \(0\)
  • \(A_i\lt X\) のとき、 \(\displaystyle \Big\lceil \frac{X - A_i}{i} \Big\rceil\)

を足したものが最小の操作回数です。これが \(K\) 以下か判定すればよいです。

解法

今回であれば、必ず \(ans\)\(1\) 以上になります。また \(A_1 + K + 1\) 未満です。( \(A_1\)\(K\) 回すべて操作を行っても届かないためです)よって、この範囲で解の二分探索をすればよいです。

上の判定問題の計算量は \(O(N)\) です。判定問題を \(O( \log (A_1 + K))\) 回解けばよいので、計算量は \(O(N \log(A_1 + K))\) となります。

実装例

n, k = map(int, input().split())
a = [0] + list(map(int, input().split())) # 1-indexed で扱えるようにする

def isok(x):
    # ans を x 以上にすることができますか?
    nk = 0
    for i in range(1, n + 1):
        if a[i] < x:
            nk += (x - a[i] + i - 1) // i # ceil((x - a[i]) / i)
            if nk > k:
                return False
    return True

ok = 1
ng = a[1] + k + 1

while ng - ok > 1:
    m = (ok + ng) // 2
    if isok(m):
        ok = m
    else:
        ng = m

print(ok)

注意

  • ceil を小数で計算しようとすると、その精度によっては WA となります。基本的には整数範囲で行うことを意識するよいでしょう。

  • overflow も注意してください。 ng も実装方針によってはしますし、判定問題内の nk の overflow は気が付きにくいです。

posted:
last update: