行列累乗で線形遷移を飛ばす
一定の線形変換をK回繰り返すなら、その変換を行列で表し二分累乗できます。
言語:Python / 計算量:d次元なら O(d³ log K)
前提:剰余と組合せを安全に扱う / DPは「同じ続きをまとめる」
考え方
- 状態ベクトルを決めます。
- 1ステップの遷移行列を書きます。
- 単位行列を初期値にして、行列積を二分累乗します。
具体例
F_{n+1}=F_n+F_{n-1} は [F_{n+1},F_n] = [[1,1],[1,0]] [F_n,F_{n-1}]。
実装
p=10**9+7
def mul(a,b):
return [[sum(a[i][k]*b[k][j] for k in range(2))%p for j in range(2)] for i in range(2)]
a=[[1,1],[1,0]];r=[[1,0],[0,1]];n=10
while n:
if n&1:r=mul(r,a)
a=mul(a,a);n>>=1
print(r[0][1])注意する条件
行列積は一般に順序を交換できません。状態を行ベクトルにするか列ベクトルにするかで式が変わります。
確認問題
F0=0,F1=1 のとき F6 は?
解答と理由
8
0,1,1,2,3,5,8です。
実装課題
N(0≤N≤10^18)。フィボナッチ数F_Nを10^9+7で割った余りを行列累乗で求める。
入力:
10
出力:
55参考実装
n=int(input());p=10**9+7
def mul(a,b):
return [[sum(a[i][k]*b[k][j] for k in range(2))%p for j in range(2)] for i in range(2)]
a=[[1,1],[1,0]];r=[[1,0],[0,1]]
while n:
if n&1:r=mul(r,a)
a=mul(a,a);n>>=1
print(r[0][1])