FunctionalGraph / Doubling

FunctionalGraph は、各頂点から出る辺がちょうど 1 本の有向グラフです。

つまり、写像 f: {0, ..., n-1} -> {0, ..., n-1} と同じものです。
競技プログラミングでは、頂点 v から k 回移動した到達点 f^k(v) を求める問題でよく出てきます。

Doubling は、このような「同じ操作を非常に多く繰り返す」処理を O(log k) で行うための technique です。

使いどころ

次のような問題でよく使います。

ABC167 D “Teleporter” や ABC367 E “Permute K times” のような問題が代表例です。

考え方

Doubling では、次の表を前計算します。

すると、k を二進展開して、立っている bit に対応する table[b] だけを順に使えば、f^k(v) が求まります。

たとえば k = 13 = 8 + 4 + 1 なら、f^13(v) は f^8, f^4, f^1 を順に適用すればよいです。

import

import atcoder/extra/graph/functional_graph

型

type Doubling
type FunctionalGraph

Doubling は一般の写像反復用です。
FunctionalGraph は to: seq[int] と Doubling をまとめた wrapper です。

コンストラクタ

proc initDoubling(to: openArray[int], maxLog: int = 60): Doubling
proc initFunctionalGraph(to: openArray[int], maxLog: int = 60): FunctionalGraph

操作

proc len(db: Doubling): int
proc jump[K: SomeInteger](db: Doubling, v: int, k: K): int
proc `[]`[K: SomeInteger](db: Doubling, q: tuple[v: int, k: K]): int

proc len(fg: FunctionalGraph): int
proc jump[K: SomeInteger](fg: FunctionalGraph, v: int, k: K): int
proc `[]`[K: SomeInteger](fg: FunctionalGraph, q: tuple[v: int, k: K]): int

proc cycleInfo(fg: FunctionalGraph, start: int): tuple[tail, period: int]
proc orbit(fg: FunctionalGraph, start: int): tuple[prefix, cycle: seq[int]]

使用例

let to = @[1, 2, 0, 4, 4]let fg = initFunctionalGraph(to, maxLog = 60)doAssert fg.jump(0, 0) == 0doAssert fg.jump(0, 1) == 1doAssert fg.jump(0, 2) == 2doAssert fg.jump(0, 3) == 0doAssert fg.jump(3, 1000000000000000000'u64) == 4

[] sugar も使えます。

doAssert fg[(0, 5)] == 2doAssert fg[(3, 100)] == 4

cycle の取得

let fg = initFunctionalGraph(@[1, 2, 3, 1, 5, 5])let info = fg.cycleInfo(0)doAssert info.tail == 1doAssert info.period == 3let orb = fg.orbit(0)doAssert orb.prefix == @[0]doAssert orb.cycle == @[1, 2, 3]

Nim らしい使い方

Nim では tuple を使って、fg[(v, k)] のように書けます。

let ans = fg[(start, k)]

ただし、読みやすさを優先する場合は fg.jump(start, k) も推奨です。

計算量

頂点数を n、B = maxLog + 1 とすると、

です。

注意

関連資料・他言語ライブラリ