結果

問題 No.1868 Teleporting Cyanmond
ユーザー floraflora
提出日時 2022-03-11 22:32:16
言語 PyPy3
(7.3.15)
結果
AC  
実行時間 276 ms / 2,000 ms
コード長 7,799 bytes
コンパイル時間 661 ms
コンパイル使用メモリ 87,124 KB
実行使用メモリ 97,236 KB
最終ジャッジ日時 2023-10-14 07:47:37
合計ジャッジ時間 7,812 ms
ジャッジサーバーID
(参考情報)
judge15 / judge14
このコードへのチャレンジ
(要ログイン)

テストケース

テストケース表示
入力 結果 実行時間
実行使用メモリ
testcase_00 AC 77 ms
71,472 KB
testcase_01 AC 74 ms
71,400 KB
testcase_02 AC 77 ms
71,212 KB
testcase_03 AC 276 ms
94,196 KB
testcase_04 AC 250 ms
88,680 KB
testcase_05 AC 149 ms
78,576 KB
testcase_06 AC 169 ms
78,964 KB
testcase_07 AC 215 ms
84,696 KB
testcase_08 AC 189 ms
81,536 KB
testcase_09 AC 209 ms
83,996 KB
testcase_10 AC 259 ms
92,336 KB
testcase_11 AC 126 ms
78,028 KB
testcase_12 AC 177 ms
80,736 KB
testcase_13 AC 210 ms
85,336 KB
testcase_14 AC 266 ms
94,916 KB
testcase_15 AC 227 ms
88,768 KB
testcase_16 AC 165 ms
79,096 KB
testcase_17 AC 180 ms
80,092 KB
testcase_18 AC 256 ms
97,112 KB
testcase_19 AC 248 ms
97,008 KB
testcase_20 AC 242 ms
96,840 KB
testcase_21 AC 249 ms
97,012 KB
testcase_22 AC 256 ms
97,236 KB
testcase_23 AC 237 ms
97,056 KB
testcase_24 AC 233 ms
97,080 KB
testcase_25 AC 241 ms
96,980 KB
testcase_26 AC 228 ms
97,016 KB
testcase_27 AC 227 ms
97,208 KB
権限があれば一括ダウンロードができます

ソースコード

diff #

# 実装上の工夫:
# 非再帰(パフォーマンス(メモリ、時間ともに)上微妙にいい、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)
        # operator
        self.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 = 0
        if right is None:
            right = self.N
        # left,rightに対応するtreeのindex
        left += self.N
        right += self.N
        # 左右の値
        vl = None
        vr = 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 += 1

            if right & 1:
                # rightが奇数なら、-1してから確定させる
                right -= 1
                if vr is None:
                    vr = self.tree[right]
                else:
                    # 順番には注意ね
                    vr = self.operator(self.tree[right], vr)
            # 親を見る
            left >>= 1
            right >>= 1

        # 最後にvlとvrの演算を返す。ただしどちらもNoneなら、区間が空集合になっているのでNoneを返す。片側がNoneならNoneじゃないほうを返すことにする。
        if vl is None and vr is None:
            return None
        elif vl is None:
            return vr
        elif vr is None:
            return vl
        else:
            # ここでも演算の順番に注意
            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] += x
        while k > 1:
            k >>= 1
            self.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.N


N=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ほかはinf
distseg=SegmentTree([float('inf')]*(N-1)+[0],operator=min)

for i in reversed(range(N-1)):
    #iからN-1の最小値を計算して
    #これはすなわち[i+1,R[i]]の最小値を計算すればOK
    mindist=distseg.query(i+1,R[i]+1)
    #mindist+1をそこの距離として更新する
    distseg.update(i,mindist+1)

#最後まで求めて0の値が答えです
print(distseg.get(0))
0