#MMA Contest 015 L Slime Structure ''' 残り2問。Kは難しそうなのでLから(?) ■ABC303D Shift vs. CapsLock aとAだけで構成された文字列Sがある。a,Shift+a,CapsLockだけで、最短で文字列Sを完成させろ。 Reference: https://atcoder.jp/contests/abc303/tasks/abc303_d ┏━━━━━━━┓┏━━━━┓ これの想定解はDPで、DP[i][j]: i文字目をCaps状態jで入力する ┃Shift ┃┃Caps ┃ 最短の時間 としてDPを回せばよいのだが、どうやらこの問題は ┃ ┏━━━┓ ┃┃Lock ┃ SegTreeに乗せることで、区間クエリ[L,R]あたりO(logN)で回答 ┃ ┃ a ┃ ┃┃ ┃ することができるらしい。 ┃ ┗━━━┛ ┃┃ ┃ 本問も同様の手法で解けるのではないだろうか。 ┗━━━━━━━┛┗━━━━┛ Reference: https://twitter.com/simasimasukiyo/status/1662630417193930754?s=20 ■考察 区間分割しないときつそうではある。 区間全幅を使うか、区間の左端だけを使うか、区間の右端だけを使うか、 みたいな感じでのり付けしていくのがベターかな。 区間を√Nで分割するとして、1回あたりの計算量はO(√N)くらいで収まりそう 多分 問題はクエリ1、一生更新するな O(√N)で更新可能、それはそう。 連長圧縮されたスライムを、クエリ1で変更されるスライムで切断しておけばいいか。 とりあえずO(Q√N)には収まる、が、制約がN<10^5でありスライム切断クエリで実質的に Nが増加することを考えるとかなり怪しい。 できればセグ木に乗せておきたい。 クエリ1で切断するぶんで区切っておき、セグ木には 1. 区間左端からの最大部分配列 2. 区間右端からの最大部分配列 3. 区間全域内での最大部分配列 4. 区間総和 を格納しておく必要がありそうだ。 そもそもこれ区間合成可能なの? 1. これらはたぶん大丈夫。採用区間左端/右端の最大部分配列を使うか、 2. それに隣の区間総和を足したものを比較すればよい。 違うな、区間全域と隣の左端、あるいは区間右端と隣の全域、のどちらかだ。 じゃあ区間全域を保持しないとだめか。 3. 区間またぎの有無で分ければいいか。 区間内最大か、区間右端と隣の左端の合成値のどちらかだろう。 4. わろす ただ今回の制約だとどれも値が爆発するので、 Python的には残念だが、これらはすべてtuple型で保持しないとだめそうだ。 苦しい。 クエリ2を手動で解いてもいいが、それするくらいなら全てSegTreeに乗せてしまえ。 ''' #Segment Tree: O(logN) class SegmentTree: # Segment Tree def __init__(self,n,identity_e,combine_f): # 適応条件: 単位元eがある、互換可能 self._n=n; self._size=1 # モノイド(単位元)の例: while self._size>=1 # self._node[i]=self._combine_f(self._node[i<<1|0],self._node[i<<1|1]) # def fold(self,L,R): # fold: 区間取得 O(logN) L+=self._size; R+=self._size # 区間 [L,R) の特定値を取得する vL,vR=[self._identity_e]*2 # while L>=1; R>>=1 # .R.5 L 7 Rの移動が変則的 return self._combine_f(vL,vR) # ←R L→ #SegTree用の関数を定義 node: (区間左端,右端,区間内 の最大スコア, 区間総長)を格納 f_n=lambda n,m:(max(n[0],n[3]+m[0]),max(m[1],n[1]+m[3]),max(n[2],m[2],n[1]+m[0]),n[3]+m[3]) import heapq as hq f=lambda:list(map(int,input().split())) #入力受取 N=int(input()); Slime=[f() for _ in range(N)]; Len=[]; total=0; INF=10**18 Q=int(input()); Tasks=[f() for _ in range(Q)]; Cut=[INF] #スライムの分割箇所を決定 for t,L,R in Tasks: if t==1: hq.heappush(Cut,L-1); hq.heappush(Cut,L) if t==2: hq.heappush(Cut,L-1); hq.heappush(Cut,R) #実際にスライムを分割 for attack,num in Slime: while num and Cut[0]<=total+num: if Cut[0]<=total: hq.heappop(Cut); continue X=Cut[0]-total; Len.append((attack,X)); num-=X; total+=X; hq.heappop(Cut) if num: Len.append((attack,num)); total+=num #セグ木に乗せる準備 ノード作成と連想配列を用意 D=dict(); Unit=[(0,0,0,0)]; total=0; D[0]=0 for index,(attack,num) in enumerate(Len,start=1): if attack>=0: Unit.append(tuple(attack*num for _ in range(4))) else: Unit.append((attack,attack,attack,attack*num)) total+=num; D[total]=index ST=SegmentTree(len(Unit),(-INF,-INF,-INF,0),f_n); ST.build(Unit) #クエリを処理 for t,L,R in Tasks: if t==1: ST.update(D[L],(R,R,R,R)) if t==2: print(ST.fold(D[L-1]+1,D[R]+1)[2])