E - 花の種類を数えよう / Count the Types of Flowers 解説 by admin
GPT 5.2 High概要
各クエリ区間 \([L,R]\) に含まれる「異なる値(品種番号)」の個数を、\(N,Q \le 10^5\) の制約下で高速に求める問題です。
考察
重要な気づき
区間内の「種類数」は、その区間内で 各品種が最後に現れる位置 だけを数えれば求まります。
たとえば \(P = [1,2,1,3,2]\) のとき、\(R=5\) まで見たとすると
- 品種1の最後の出現: 位置3
- 品種2の最後の出現: 位置5
- 品種3の最後の出現: 位置4
このとき区間 \([L,5]\) に含まれる種類数は、「最後の出現位置が \(L\) 以上にある品種の数」と一致します。
素朴解がダメな理由
各クエリごとに \([L,R]\) を走査して集合で数えると \(O((R-L+1))\) かかり、最悪で \(Q\) 回あるので \(O(NQ)\) となり、\(10^{10}\) 規模でTLEになります。
どう解決するか
右端 \(R\) を小さい順にクエリを並べ、左から順に配列を見ながら「最後の出現位置」情報を更新していきます。
このとき「最後の出現位置たち」を 1 次元上の点として管理し、区間和で数えられるようにすると高速です。
アルゴリズム
Fenwick木(BIT) を使って、次を実現します。
- BITの各位置 \(i\) に値 \(1/0\) を持たせる
- 「位置 \(i\) が、ある品種の 現在の最後の出現位置 なら 1」
- そうでなければ 0
- したがって、ある時点(ある \(R\) まで処理済み)での \([L,R]\) の種類数は BIT の区間和 \(\sum_{i=L}^{R} \text{BIT}[i]\) で求まります。
処理手順は以下です。
- クエリ \((L_i,R_i)\) を「\(R_i\) 昇順」にソートして保持する(元の順番に戻すため index も持つ)。
- \(i=1\) から \(N\) まで順に見ていく(現在位置を \(i\) とする)。
- 値 \(v=P_i\) について、直前の出現位置
prev = last[v]を見る。prev > 0なら、そこは「最後の出現」ではなくなるので BITでadd(prev, -1)。- 新しい最後の出現位置として BITで
add(i, +1)。 last[v] = iに更新。
- 「右端がちょうど \(R=i\) のクエリ」をすべて処理し、答えを
\(\text{BIT.range\_sum}(L, R)\) で求める。
小さな例
\(P=[1,2,1,3,2]\) を左から処理すると、\(i=5\) 時点では BIT には
最後の出現位置(3,4,5)にだけ 1 が立っています。
よってクエリ \([2,5]\) の答えは BIT の \(2..5\) の和で \(3\)(品種1,2,3)になります。
計算量
- 時間計算量: \(O\big((N+Q)\log N\big)\)
(各要素更新が \(\log N\)、各クエリ回答が \(\log N\)) - 空間計算量: \(O(N+Q)\)
(BIT配列、last配列、クエリ配列、答え配列)
実装のポイント
クエリを \(R\) でソートし、配列を左から走査しながら「今処理できるクエリ」をまとめて答えます(オフライン処理)。
last[v]は「品種 \(v\) が最後に出現した位置」。同じ品種が再登場したら、前の位置を-1して新しい位置を+1します。BITは 1-indexed で実装すると典型的でバグりにくいです(コードでも
P=[0]+...として調整)。入力が大きいので
sys.stdin.buffer.read()でまとめ読みすると高速です。ソースコード
import sys
class Fenwick:
__slots__ = ("n", "bit")
def __init__(self, n):
self.n = n
self.bit = [0] * (n + 1)
def add(self, i, x):
n = self.n
bit = self.bit
while i <= n:
bit[i] += x
i += i & -i
def sum(self, i):
s = 0
bit = self.bit
while i > 0:
s += bit[i]
i -= i & -i
return s
def range_sum(self, l, r):
return self.sum(r) - self.sum(l - 1)
def main():
data = list(map(int, sys.stdin.buffer.read().split()))
it = iter(data)
N = next(it)
Q = next(it)
P = [0] + [next(it) for _ in range(N)]
queries = []
for idx in range(Q):
l = next(it); r = next(it)
queries.append((r, l, idx))
queries.sort()
fw = Fenwick(N)
last = [0] * (N + 1)
ans = [0] * Q
qi = 0
for i in range(1, N + 1):
v = P[i]
prev = last[v]
if prev:
fw.add(prev, -1)
fw.add(i, 1)
last[v] = i
while qi < Q and queries[qi][0] == i:
r, l, idx = queries[qi]
ans[idx] = fw.range_sum(l, r)
qi += 1
sys.stdout.write("\n".join(map(str, ans)))
if __name__ == "__main__":
main()
この解説は gpt-5.2-high によって生成されました。
投稿日時:
最終更新: