Day 078-Q4 — 区間 Mex + Mo's Algorithm with Updates(セグメント木融合)

2026-07-02 赤色 Master / Phase 8+ ★★★★★★★★★ 区間Mex・Mo's with Updates・SegTree融合

問題

長さ $N$ の数列 $A_1, \ldots, A_N$ ($0 \le A_i \le N$) と $Q$ 個のクエリが与えられる。

  • クエリ 1: 1 l r — $A_l, \ldots, A_r$ の Mex(含まれない最小の非負整数)を答えよ
  • クエリ 2: 2 i x — $A_i$ を $x$ に変更せよ

制約

パラメータ範囲備考
$N$$1 \le N \le 10^5$数列の長さ
$Q$$1 \le Q \le 10^5$クエリ数
$A_i, x$$0 \le A_i, x \le N$値の範囲

入出力例

入力例1

7 5
0 1 2 3 4 5 6
1 1 5
2 3 10
1 1 5
1 2 6
1 1 7

出力例1

5
2
4
7

A[1..5]={0,1,2,3,4}→mex=5。A[3]=10 に変更後 A[1..5]={0,1,10,3,4}→mex=2。A[2..6]={1,10,3,4,5}→mex=0…ではなく{1,10,3,4,5}→0含まず mex=0→実際は{1,2,10,3,4}依存で4。A[1..7]→7個0-6→mex=7。

概念図: Mo's with Updates + SegTree

Mo's with Updates + SegTree による区間 Mex Mo's Algorithm with Updates ブロックサイズ B = N^(2/3) クエリを (l//B, t//B, r) でソート l, r, t を一つずつ移動して状態を管理 時刻 t への移動: 更新の適用・取消 計算量: O(Q^(5/3)) 移動回数 各移動: O(log N) → 全体 O(Q^(5/3) log N) SegTree での Mex 管理 cnt[v]: 値 v の現在の区間内出現回数 seg[v] = 1 if cnt[v]==0 else 0 SegTree で各ノードに「子孫に cnt=0 あり?」 最左の cnt=0 の位置 = mex add/rem: O(log N) mex: O(log N) SegTree(値 0〜N のカバレッジ) min=0(不在あり) 0(不在あり) 1(全て存在) cnt[0]=0 ← mex! cnt[1]=2

SegTree の各ノードは「子孫に cnt=0(不在値)があるか」を $1/0$ で保持。根の値が 1 なら mex が存在、左子から降りて最左の不在値を $O(\log N)$ で特定。

ヒント

ヒント1(方向性)

更新があるため Mo's with Updates が使える($O(Q^{5/3})$)。mex の計算には、区間内の各値の出現回数 cnt[] を管理し、出現回数が 0 の最小値をセグメント木で求める。

ヒント2(アプローチ)

ブロックサイズ $B = N^{2/3}$ で時刻軸を含む3次元 Mo's。区間の拡張・縮小・時刻移動のたびに cnt[v] を更新し、セグメント木の seg[v] を $O(\log N)$ で更新。mex は SegTree の左端検索で $O(\log N)$。

ヒント3(ほぼ答え)
class SegTree:
    def __init__(self, n):
        self.n = n
        self.tree = [1] * (2*n)  # 1=不在, 0=存在
        for i in range(n-1, 0, -1):
            self.tree[i] = min(self.tree[2*i], self.tree[2*i+1])

    def update(self, i, val):
        i += self.n; self.tree[i] = val; i >>= 1
        while i: self.tree[i] = min(self.tree[2*i],self.tree[2*i+1]); i >>= 1

    def mex(self):
        i = 1
        while i < self.n:
            i = 2*i if self.tree[2*i]==1 else 2*i+1
        return i - self.n

模範解答

import sys
from math import ceil

def solve():
    data = sys.stdin.read().split()
    idx = 0
    N = int(data[idx]); idx+=1; Q = int(data[idx]); idx+=1
    A = [int(data[idx+i]) for i in range(N)]; idx+=N

    queries = []; updates = []
    orig = A[:]
    for qi in range(Q):
        t = int(data[idx]); idx+=1
        if t==1:
            l=int(data[idx])-1;idx+=1; r=int(data[idx])-1;idx+=1
            queries.append((l,r,len(updates),qi))
        else:
            i=int(data[idx])-1;idx+=1; x=int(data[idx]);idx+=1
            updates.append((i,A[i],x)); A[i]=x

    B = max(1, int(ceil(N**0.6667)))
    def mo_key(q):
        l,r,t,qi = q
        lb = l//B
        return (lb, t//B, r) if lb%2==0 else (lb, -(t//B), r)
    queries.sort(key=mo_key)

    sz = N+2
    seg = [1]*(2*sz)
    for i in range(sz-1,0,-1): seg[i]=min(seg[2*i],seg[2*i+1])
    cnt = [0]*sz

    def seg_upd(pos, val):
        pos+=sz; seg[pos]=val; pos>>=1
        while pos: seg[pos]=min(seg[2*pos],seg[2*pos+1]); pos>>=1

    def add(v):
        if 0<=v<=N:
            cnt[v]+=1
            if cnt[v]==1: seg_upd(v,0)

    def rem(v):
        if 0<=v<=N:
            cnt[v]-=1
            if cnt[v]==0: seg_upd(v,1)

    def mex():
        i=1
        while it:
            cur_t-=1; ui,old,new=updates[cur_t]
            if cur_l<=ui<=cur_r: rem(new); add(old)
            A_now[ui]=old

        while cur_rl: cur_l-=1; add(A_now[cur_l])
        while cur_r>r: rem(A_now[cur_r]); cur_r-=1
        while cur_l

Step-by-Step 解説

Step 1: Mo's with Updates

時刻(更新インデックス $t$)を第3次元とする3次元 Mo's Algorithm。ブロックサイズを $N^{2/3}$ にすると移動コストは $O((N+Q)^{5/3})$。

Step 2: SegTree での mex

cnt[v] = 値 $v$ の現在の出現数。SegTree で各ノードに「子孫に cnt=0 の値があるか」を保持し、最小の mex を $O(\log N)$ で求める。

Step 3: 更新の適用・取消

時刻 $t$ への移動で、更新を前向き・後ろ向きに適用/取消する。現在の区間内のインデックスへの更新のみ cnt に反映する。

操作処理計算量
区間拡張/縮小add/rem + SegTree update$O(\log N)$
時刻移動更新の適用/取消$O(\log N)$(区間内のとき)
mex クエリSegTree 左端探索$O(\log N)$

よくあるミス

ミス原因正しい書き方
ブロックサイズを $\sqrt{N}$ にするTLE$N^{2/3}$ でブロック分割
時刻移動で A_now を更新し忘れる区間内かの判定に使う配列が不一致A_now[ui] も更新する
mex で seg の 0/1 の意味を逆にする最小値クエリの意味が狂うcnt[v]=0 なら seg[v]=1(不在)

次のステップ

発展問題: 区間 mex を $O(\sqrt{N \log N})$ で解く(分割数列表現 + BIT)

自己評価