aacord’s memo

abcを中心にpythonで解いた問題のメモ、整理をしています。緑になった。

CPSCO2019 Session1 E - Exclusive OR Queries (python)

python だと難易度が爆上がりするやつ。
E - Exclusive OR Queries


本編まで飛ばしていいよ。
やるべき動作は
1.特定の範囲 [L,R] の XOR を出力する
2.[L,R] にある数字を全て削除する
3.[L,R] に存在する数の個数分だけ、ソートを維持して数 x を追加
ですが、これを全て log(N) とかでやってくれるものは python にはありません。
(実は近いものは存在していて後ろの方で書きます。)
まず、XOR の性質に注目すると、a^a = 0 なので、list(A) にある数字のなかで、k回でてくるものは k%2 回出てくると考えていいです。A = [1,2,2,3,3,3,4,4,4,4] なら A = [1,3] と出来るということです。
同じように x を追加する回数も 2 で割った余りだけ行えば十分です。
なので、動作2.はうまく A に存在しているものだけを削除することができれば、Q 回 1.~3. のの動作を行ったときに [L,R] に存在する数を求める回数は最大でも Q になることが分かります。
また、数字を削除するという動作は XOR の場合 a^0 = a なので、数字を 0 に置き換えるとして考えることができます。
なので区間取得・一点更新ができるセグ木と、BIT木の両方を導入して、
先にクエリを読み、A と x の種類の分だけ、セグ木で値を管理、BIT木で要素の数を管理することで、
1.→ セグ木で区間取得
2.→ BIT木で個数が i 以上になる最小の index 二分探索 → その index にあたる値、要素を0に更新
3.→ セグ木で一点更新、BITで一点更新(BIT木は更新そのものはできないので、加算をうまく使う)
でなんとか実現できます。
N = len(A) とすると計算量は
1 : O(Q*log(N+Q)), 2 : 最大 O(Q*log(N+Q)) 3 : log(N+Q) で、最初にクエリ先読みして座標圧縮する過程で O(Q), O(N+Q) 、区間を探すのに O(log(N+Q)) かかります。(多分)
ほんとにギリギリ耐えた。(2999ms)

import sys
read = sys.stdin.buffer.read
readline = sys.stdin.buffer.readline
readlines = sys.stdin.buffer.readlines
from bisect import bisect,bisect_left
from collections import Counter

n,q = map(int,readline().split())
a = list(map(int,readline().split()))
c = Counter(a)
s = set(a)
chk = []
for i in range(q):
  l,r,x = map(int,readline().split())
  chk.append([l,r,x])
  s.add(x)
  
s = sorted(list(s)) #座標圧縮
def compress(list1):
    list2 = sorted(set(list1))
    memo = {value : index for index, value in enumerate(list2)}
    return memo, len(list2)
memo, len_memo = compress(s)

class Bit: # BIT
    def __init__(self, n):
        self.size = n
        self.tree = [0] * (n + 1)
        self.depth = n.bit_length()
 
    def sum(self, i):
        s = 0
        while i > 0:
            s += self.tree[i]
            i -= i & -i
        return s
 
    def add(self, i, x):
        while i <= self.size:
            self.tree[i] += x
            i += i & -i
 
    def lower_bound(self, x): #累積和がx以上になる最小のindexを返す
        sum_ = 0
        pos = 0
        for i in range(self.depth, -1, -1):
            k = pos + (1 << i)
            if k <= self.size and sum_ + self.tree[k] < x:
                sum_ += self.tree[k]
                pos += 1 << i
        return pos + 1
      
class segment_():
    def __init__(self,n,ide_ele,segfunc):
        self.ide_ele = ide_ele
        self.num = 1 << n.bit_length()
        self.seg = [self.ide_ele] * 2 * self.num
        self.segfunc = segfunc
                
    def update(self, k, r):
        k += self.num
        self.seg[k] = r
        while k:
            k >>= 1
            self.seg[k] = self.segfunc(self.seg[k * 2], self.seg[k * 2 + 1])

    def query(self, p, q):
        # qは含まない
        if q < p:
            return self.ide_ele
        p += self.num;
        q += self.num
        res = self.ide_ele
        while p < q:
            if p & 1 == 1:
                res = self.segfunc(res, self.seg[p])
                p += 1
            if q & 1 == 1:
                q -= 1
                res = self.segfunc(res, self.seg[q])
            p >>= 1;
            q >>= 1
        return res

S = segment_(len_memo,0,lambda a, b: a^b)
num = Bit(len_memo)
for i in set(a):
  if c[i]%2 == 1: # 2で割った個数分だけ考えればいい
    ind = memo[i]
    num.add(ind+1,1)
    S.update(ind+1,i)
  
for i in range(q):
  l,r,x = chk[i]
  L = bisect_left(s,l) #区間を求める
  R = bisect(s,r)
  if R > L:
    ans = S.query(L+1,R+1) # 1, 区間取得
    sl = num.sum(L) #区間にある個数を求める
    ss = num.sum(R)-sl
    if ss > 0:
      hp = ss
      while hp > 0: # 2
        ind = num.lower_bound(sl+1) #区間に存在する数を抽出
        num.add(ind,-1) #削除する
        S.update(ind,0)  #削除する
        hp -= 1
      to = memo[x]+1 # 3
      RR = num.sum(to)
      LL = num.sum(to-1)
#xの個数が奇数になるなら 1 つだけxが存在しているように更新
#xの個数が偶数になるなら xが存在していないように更新
      if RR == LL:
        num.add(to,ss%2) 
        S.update(to,ss%2*x)
      else:
        if (1+ss)%2 == 0: 
          num.add(to,-1)
        S.update(to,(1+ss)%2*x)
    print(ans)    
  else:
    print(0)

本編
python でも二分木そのものが使いたいよね。
これです。本当にきりさんありがとうございます。
平衡二分木を実装する - Qiita
これ一つで、
heap(ソートを維持して追加、最大最小出力)
特定の区間の最大最小の出力
一点削除
値が存在しているかの判定
ができるそうです。もう全部これでいいんじゃないかな。

今回は動作3.はそのままできるので、1. 2. がうまくできれば終わりです。
[L,R] に存在する数字は、特定の区間の最小値の出力を [L,R] で繰り返すことで過不足なく求めれるので、小さい順に XOR の計算と値の削除を並行して処理したら実現できます。
計算量もなんと座圧しなくても 1+2 : O(Q*log(log(max(A))))
3 : O(Q*log(log(max(A)))) ででき、前処理もないので普通に間に合います。
前処理して座圧すると、O(Q) + O(Q*log(log(Q+N))) + O(Q*log(log(Q+N))) とかになります。(多分)
出してみた。2043ms
Submission #14426077 - CPSCO2019 Session1