Typical DP Contest T - フィボナッチ
問題
数列aiをai = 1(i ≦ K)
ai = ai-1 + ai-2 + ... + ai-K (i > K)
と定義するとき、aNをmod 10^9 + 7で求めよ。
制約条件
K≦1000
N≦10^9
方針
漸化式を行列の累乗で計算するとO(K^3logN)かかって無理なので、
漸化式のの高速計算法を使うと解ける。
(wataさんがきたまさ法と呼んでいた奴http://d.hatena.ne.jp/wata_orz/20090922/1253615708)
この問題では漸化式に定数項がないので、定数項なしで解説する。
定数項がある場合は項が一個くっついて面倒なだけで、本質的には変わらない。
amをa0からaK-1の線形結合で表していくのが基本方針。
amが表せてるとき、a2mはa0からa2K-2で表せる。
何故なら、
am = Σkiaiと書けるとき
a2m = Σkiai+m (漸化式をm項ずらす)
= Σki(Σkjai+j) (上の漸化式をi項ずらす)
と書けるため。
で、aK〜a2K-2は、愚直に一番最初の漸化式を使ってa0〜aK-1で表してやればよい。
こうすれば、amからa2mを求められるので、
繰り返し二乗法的なことをすればaNがO(K^2 logN)で求められる。
ソースコード
typedef deque<int> di; const int mod = inf + 7; int K, N; inline void advance(di &co){ //amをa0〜aK-1で表しているとき、am+1の表現を求める int n = co.size(); co.push_front(0); rep(i, n) (co[i] += co[n]) %= mod; co.pop_back(); } inline void dbl(di &v){ //amをa0〜aK-1で表しているとき、a2mの表現を求める int n = v.size(); di prev = v; v.clear(); v.resize(n * 2); rep(i, n) rep(j, n) (v[i + j] += (ll)prev[i] * prev[j] % mod) %= mod; di co(K, 1); for(int i = n; i < 2 * n; i++){ rep(j, n) (v[j] += (ll)v[i] * co[j] % mod) %= mod; advance(co); } rep(i, n) v.pop_back(); } int main(){ cin >> K >> N; N--; di v(K, 0); v[1] = 1; int p; for(p = 31; !(N >> p & 1); p--); p--; for(; p >= 0; p--){ dbl(v); if(N >> p & 1) advance(v); } ll ans = 0; rep(i, K) ans += v[i]; cout << ans % mod << endl; return 0; }
これでtypical DP全部解いた。
後は解説記事書いてないのがG, H, I, J, K, L, M, N, R, S