ABC227Dと内容はほとんど同じですが,計算を効率的にしなければなりません.
【類題】
方法1
考え方
まず,整数$a$が何回出てきたかをカウントすると,ABC227Dと同じ問題になる.ただし,今回は$N$回計算しなければならない.与えられた$\{A_{i}\}$は座標圧縮して,カウント結果をcnt
というリストで表す.
$O(N)$の計算
\begin{aligned}
\sum_{i \in S} \min(x, \mathrm{cnt}[i])
\end{aligned}
を$N$回計算すると,最悪$O(N^{2})$でTLEする.したがって,この和をもっと効率的に計算しなければならない.\sum_{i \in S} \min(x, \mathrm{cnt}[i])
\end{aligned}
cnt
をソートすると,ind = min(bisect_left(cnt, x), len(cnt))
に対して
\begin{aligned}
& \sum_{i \in S} \min(x, \mathrm{cnt}[i]) \\
&= \sum_{0 \leq i\leq \mathrm{ind}} \mathrm{cnt}[i] + \sum_{i > \mathrm{ind}} x\\
&= \sum_{0 \leq i \leq \mathrm{ind}} \mathrm{cnt}[i] + x (|S| - \underbrace{(\mathrm{ind} + 1)}_{\hspace{-5em}\mathrlap{\text{第1項で和を取った個数}}})
\end{aligned}
となる.よって,& \sum_{i \in S} \min(x, \mathrm{cnt}[i]) \\
&= \sum_{0 \leq i\leq \mathrm{ind}} \mathrm{cnt}[i] + \sum_{i > \mathrm{ind}} x\\
&= \sum_{0 \leq i \leq \mathrm{ind}} \mathrm{cnt}[i] + x (|S| - \underbrace{(\mathrm{ind} + 1)}_{\hspace{-5em}\mathrlap{\text{第1項で和を取った個数}}})
\end{aligned}
cnt
の累積和が計算できていれば,和は$O(1)$で計算できる.あとはABC227Dと同じように二分探索で
\begin{aligned}
\sum_{i \in S} \min(x, \mathrm{cnt}[i])
\geq x \cdot K
\end{aligned}
となる最大の$x$を求めれば良い.\sum_{i \in S} \min(x, \mathrm{cnt}[i])
\geq x \cdot K
\end{aligned}
$K = 1,2, ..., N$に対して二分探索を2回行うので,$O(N (\log N)^{2})$で解けた.
回答例
from bisect import bisect_left N = int(input()) A = list(map(int, input().split())) D = {a:i for i, a in enumerate(sorted(list(set(A))))} cnt = [0] * len(D) for a in A: cnt[D[a]] += 1 cnt.sort() acc = [0] * (len(D) + 1) for i in range(len(D)): acc[i + 1] = acc[i] + cnt[i] def my_sum(x): ind = min(bisect_left(cnt, x), len(D)) return acc[ind] + x * (len(D) - ind) def check(K, x): if K * x <= my_sum(x): return True else: return False for K in range(1, N + 1): ok = 0 ng = 10 ** 6 while ng - ok > 1: mid = (ok + ng) // 2 if check(K, mid): ok = mid else: ng = mid print(ok)
方法2
考え方
上で,よく考えれば累積和ではなく\begin{aligned}
& \sum_{i \in S} \min(x, \mathrm{cnt}[i]) \\
&= \sum_{0\leq i \leq \mathrm{ind}} \mathrm{cnt}[i] + x (|S| - (\mathrm{ind} + 1))
\end{aligned}
自体を予め(ループの外で)計算できる($O(N)$).& \sum_{i \in S} \min(x, \mathrm{cnt}[i]) \\
&= \sum_{0\leq i \leq \mathrm{ind}} \mathrm{cnt}[i] + x (|S| - (\mathrm{ind} + 1))
\end{aligned}
よって,全体の計算量は$O(N \log N)$となる.
回答例
N = int(input()) A = list(map(int, input().split())) D = {a:i for i, a in enumerate(sorted(list(set(A))))} cnt = [0] * len(D) for a in A: cnt[D[a]] += 1 cnt.sort() SUM = [0] * (10 ** 6 + 1) acc = 0 used = 0 for x in range(10 ** 6 + 1): while used < len(cnt) and cnt[used] <= x: acc += cnt[used] used += 1 SUM[x] = acc + x * (len(cnt) - used) def check(K, x): if K * x <= SUM[x]: return True else: return False for K in range(1, N + 1): ok = 0 ng = 10 ** 6 while ng - ok > 1: mid = (ok + ng) // 2 if check(K, mid): ok = mid else: ng = mid print(ok)
方法3
考え方
さらに,二分探索も省略することができる.各$K$に対し
\begin{aligned}
\sum_{i \in S} \min(x, \mathrm{cnt}[i])
\geq x \cdot K
\end{aligned}
を満たす最大の$x$を求める問題は,各$K$に対し\sum_{i \in S} \min(x, \mathrm{cnt}[i])
\geq x \cdot K
\end{aligned}
\begin{aligned}
\biggl\lfloor \frac{\sum_{i \in S} \min(x, \mathrm{cnt}[i])}{x} \biggr \rfloor
\geq K
\end{aligned}
を満たす最大の$x$を求める問題に置き換えられる($K$は非負整数).\biggl\lfloor \frac{\sum_{i \in S} \min(x, \mathrm{cnt}[i])}{x} \biggr \rfloor
\geq K
\end{aligned}
「方法2」を書き換えることで,左辺は予め計算できる.
また,左辺は$x$について単調減少だから,$x$を順に見ていけば$O(N)$で$K=1,2...,N$に対して上の条件を満たす最大の$x$を探索できる.
回答例
N = int(input()) A = list(map(int, input().split())) D = {a:i for i, a in enumerate(sorted(list(set(A))))} cnt = [0] * len(D) for a in A: cnt[D[a]] += 1 cnt.sort() SUM = [0] * (10 ** 6 + 1) acc = 0 used = 0 for x in range(1, 10 ** 6 + 1): while used < len(cnt) and cnt[used] <= x: acc += cnt[used] used += 1 SUM[x] = (acc + x * (len(cnt) - used)) // x x = 0 ans = [0] * N for K in range(N, 0, -1): while SUM[x + 1] >= K: x += 1 ans[K - 1] = x print(*ans, sep = '\n')