結果
問題 | No.1868 Teleporting Cyanmond |
ユーザー |
![]() |
提出日時 | 2022-03-11 22:32:16 |
言語 | PyPy3 (7.3.15) |
結果 |
AC
|
実行時間 | 216 ms / 2,000 ms |
コード長 | 7,799 bytes |
コンパイル時間 | 623 ms |
コンパイル使用メモリ | 82,432 KB |
実行使用メモリ | 96,256 KB |
最終ジャッジ日時 | 2024-09-16 02:54:44 |
合計ジャッジ時間 | 5,572 ms |
ジャッジサーバーID (参考情報) |
judge4 / judge1 |
(要ログイン)
ファイルパターン | 結果 |
---|---|
sample | AC * 3 |
other | AC * 25 |
ソースコード
# 実装上の工夫:# 非再帰(パフォーマンス(メモリ、時間ともに)上微妙にいい、stackで書いたものとかではない)# nodeの数を2のべきにしない(微妙にメモリ節約。あと無駄なものを持たなくていいので微妙に気持ちがいい)# またnodeの数を2のべきにしなくていいと、identityの指定が不要になり、うれしい(ただしidentityの指定を不要にするとqueryの実装はちょっとだけ複雑になる)。# 交換法則は仮定しない(ただし結合法則は仮定される、例えば引き算や割り算は結合法則が満たされないためだめ、行列の積とかはいける)# 1-indexed。0-indexedでも実装できますし最初はそうしましたが、1-indexedだと親子のindexを得るのを全部bit演算でできるのではやくなります。比べてみところ以外と無視できないほど変わったので1-indexedにしました。遅い方を採用する意味はまったくないので# よく使うoperatorと単位元のリスト。"""最小値operator=min, identity=float('inf')最大値operator=max, identity = -float('inf')区間和operator=lambda x, y: x + y,identity = 0区間積operator=lambda x, y: x * y, identity = 1最大公約数operator=math.gcd, identity = 0複数の要素をもたせたいときの区間和。ベクトルの和にする。このときはupdate(k,x)のxにも同じ次元のリストを入れます。operator=lambda A, B: [a+b for a,b in zip(A,B)]"""# セグメント木のクラス。構築O(N),update O(log(N)),query O(log(N))class SegmentTree:# O(N)# 初期化です。デフォルトはRMQにしてあります。適切なoperatorを指定してください。def __init__(self, A, operator=min):"""A:いま扱う配列operator:いま考える二項間演算子。交換法則は仮定しないidentity:operatorに対応する単位元"""# ちなみに1-indexでnode iの親:i//2,左の子:2*i,右の子:2*i+1です。# ただしbit演算のほうが速いので、親:i>>1,左の子:i<<1,右の子i<<1|1と書くことにする。# セグメント木は完全二分木ですから、リストで木の要素を管理することができます。# 要素数self.N = len(A)# operatorself.operator = operator# 木を成すサイズ2N-1の配列を作る。このときidentityで初期化する。ちなみに2*NよりもN+Nのほうがかすかにはやいです。self.tree = [None] * (self.N + self.N)# 初期化は葉、すなわちtreeの後ろからN個の部分にAの値を配置する。for i, a in enumerate(A, self.N):self.tree[i] = a# たつの子にoperatorを作用したものを親にするという操作を繰り返す。ここでindexの大きい方からやっていけば葉から上にたどることができる。for i in range(self.N - 1, 0, -1):# [1,N-1]の逆順ループ。親の値をふたつの子のoperatorから決めます。ここで左右という順番でいれないとだめなことには注意ね。self.tree[i] = self.operator(self.tree[i << 1], self.tree[i << 1 | 1])# O(log(N))# Aのk番目の値A[k]をxに変更します。def update(self, k, x):# A[k]に対応するのはtree[k+self.N]ですので、そこから初めてrootに到達するまで親に向かいますk += self.N# 更新self.tree[k] = x# rootはk=1ですので正である間ループすればOKですwhile k > 1:# 親のindex。k >>= 1# 親の値を書き直すself.tree[k] = self.operator(self.tree[k << 1], self.tree[k << 1 | 1])# O(log(N))# 半開区間[left,right)において、operatorで指定された演算の値を返します。# ただしleft,rightにNoneを入力すると、例えばleft=Noneにするといまありうる一番左を指定しますdef query(self, left, right):if left is None:left = 0if right is None:right = self.N# left,rightに対応するtreeのindexleft += self.Nright += self.N# 左右の値vl = Nonevr = None# 非再帰の実装ではleaf側からrootに向かっていくのがポイントです。[2]の図がわかりやすいです。# [left,right)の外側から値を確定させていくイメージ。どんどん内側によせていき、leftとrightが重なったら終わりwhile left < right:if left & 1:# leftが奇数なら、その区間を確定させるif vl is None:# 何もなければその値自体を入れるvl = self.tree[left]else:# あればもとの値と演算vl = self.operator(vl, self.tree[left])# 次は右の親に移動するのでLを足しておくleft += 1if right & 1:# rightが奇数なら、-1してから確定させるright -= 1if vr is None:vr = self.tree[right]else:# 順番には注意ねvr = self.operator(self.tree[right], vr)# 親を見るleft >>= 1right >>= 1# 最後にvlとvrの演算を返す。ただしどちらもNoneなら、区間が空集合になっているのでNoneを返す。片側がNoneならNoneじゃないほうを返すことにする。if vl is None and vr is None:return Noneelif vl is None:return vrelif vr is None:return vlelse:# ここでも演算の順番に注意return self.operator(vl, vr)# O(log(N))# Aのk番目の値A[k]にxを加算します。すなわちA[k]+=xと同じ操作です。# 実装はupdateとほぼ同じ。def add(self, k, x):k += self.N# 加算self.tree[k] += xwhile k > 1:k >>= 1self.tree[k] = self.operator(self.tree[k << 1], self.tree[k << 1 | 1])# O(1)# A[k]と同じ。もとのAのk番目の値を取得します。def get(self, k):return self.tree[k + self.N]# O(1)# もとのAはself.treeのself.tree[self.N: 2*self.N]の部分に埋め込まれています。# その範囲を半開区間で返しますdef original_range(self):return self.N, 2 * self.NN=int(input())R=list(map(lambda x:int(x)-1,input().split()))#グラフの問題的に求めたいが、edgeが多いのでそのまま探索すると時間がかかりすぎる。#後ろから順番にNへの最短距離をもとめていく。DAGであることをイメージして効率的に探索できる#iからN-1の距離をdist[i]とする#後ろから調べて、dist[i]は[i+1,R[i]]の最小値を取ればいい。これをセグ木でやる。#初期値はN-1だけが0ほかはinfdistseg=SegmentTree([float('inf')]*(N-1)+[0],operator=min)for i in reversed(range(N-1)):#iからN-1の最小値を計算して#これはすなわち[i+1,R[i]]の最小値を計算すればOKmindist=distseg.query(i+1,R[i]+1)#mindist+1をそこの距離として更新するdistseg.update(i,mindist+1)#最後まで求めて0の値が答えですprint(distseg.get(0))