線形漸化式
線形漸化式の第$n$項を求める様々な方法を紹介する. ここでは例として,代表的な線形漸化式であるフィボナッチ数列の第$n$項を$\mod 10^9$で求めることにする.計算量は,$k$項間の線形漸化式の場合のものを示す.
愚直
第$3$項,第$4$項,第$5$項と定義通り順に求めていく方法.計算量は$O(kn)$. 第$n$項までの全ての項の値がほしいときに有効である.
long N;cin>>N;
vector<mint> fibo={1,1};
for(int i=3;i<=N;i++){
int a = (int)fibo.size();
fibo.push_back(fibo[a-1]+fibo[a-2]);
}
cout<<fibo.back()<<endl;
行列累乗
ダブリングの考え方によって,$O(k^3\log n)$で求まる. 行列の掛け算自体がコストが高く,$k$が大きいときに時間がかかる.
long N;cin>>N;
Matrix<mint> mat(2,2);
mat[0][0] = 1;
mat[0][1] = 1;
mat[1][0] = 1;
mat[1][1] = 0;
auto ans = pow(mat,N-1);
cout<<ans[0][0]<<endl;
きたまさ法
これもダブリングである. 計算量は,$O(k^2\log n)$である. 参考(誤植がある気がする)
$k$項漸化式版はこちら.
vector<mint> kitamasa(long n,const vector<mint>&d,const int k){
//f(n)の係数ベクトルを求める.
if(n==k){
return d;
}
if(n&1||n<k*2){
vector<mint> res(k);
auto x1 = kitamasa(n-1,d,k);
for(int i=0;i<k;i++){
res[i] = d[i]*x1[k-1] + (i>0?x1[i-1]:0);
}
return res;
}else{
vector<mint> res(k,0);
auto x0 = kitamasa(n/2,d,k);
auto x1 = x0;
for(int l=0;l<k;l++){//f(n/2+l)
for(int j=0;j<k;j++){
res[j] += x0[l]*x1[j];
}
vector<mint> next(k);
for(int i=0;i<k;i++){
next[i] = d[i]*x1[k-1] + (i>0?x1[i-1]:0);
}
swap(x1,next);
}
return res;
}
}
int main(){
vector<mint> d = {1,1};
auto a = kitamasa(N-1,d,2);//N-1が2以下だと壊れる
cout<<a[0]*d[0]+a[1]*d[1]<<endl;
}