0w1

Yuki 320 眠れない夜に ( Math, Fibonacci )

No.320 眠れない夜に - yukicoder

かなりフィボナッチ数列の勉強になったので、ちょっとノートをします。pekempey さんの記事を参考にしました。

ある項目に 1 少なく数えるのは、その項目から \large -1, -1, -2, -3, -5, ... と負のフィボナッチ数列を並列して進ませる事と同じで、縦の総和が結果の数になる。解説にわかりやすいイメージがあるので、ちょっと借ります。
f:id:h0rnet:20161212170814p:plain

つまり、 \large Fib( N ) - M をフィボナッチ数列中の数の総和で表したい。その上使う数を最少化したい。
その存在性はゼッケンドルフの定理 にあるように再帰的に示せる。

使う数を最少化するために、これらを知っておきたい:
 \large \sum\limits_{i=1}^n Fib( i ) = Fib( n + 2 ) - 1
 \large \sum\limits_{i=1}^n Fib( 2 * i ) = Fib( 2 * n + 1 ) - 1
 \large \sum\limits_{i=1}^n Fib( 2 * i - 1 ) = Fib( 2 * n )
証明は難しくないですが、リンクを載せます。

 \large Fib( x ) ≤ goal = Fib( N ) - M < Fib( x + 1 )
として、式 1 から  \large \sum\limits_{i=1}^{x-2} Fib( i ) = Fib( x ) - 1 < Fib( x ) < goal に導く。\large Fib( x - 1 ) \large Fib( x ) のうち一つは使わなければいけない事がわかる。次に、式 2 と 3 と「連続するフィボナッチ数は使わない事」から、同じく上限を見積もってみると \large Fib( x ) を使う事が常に最善だという事がわかる。

この貪欲法で大体行けばいいのですが、「1 回目と 2 回目は決して間違わない」から、\large Fib( N ) \large Fib( N - 1 ) を使ってはいけない事になるが、\large Fib( N ) = M の場合の答えは直接 0、\large Fib( N ) < M の場合 -1 とすれば、残りのケースは \large Fib( N ) > goal なので全部辻褄が合う。

int N;
ll M;

void init(){
  cin >> N >> M;
}

vl fib;

void preprocess(){
  fib = vl( 80 + 1 );
  fib[ 1 ] = fib[ 2 ] = 1;
  for( int i = 3; i <= 80; ++i )
    fib[ i ] = fib[ i - 1 ] + fib[ i - 2 ];
  if( fib[ N ] < M )
    cout << -1 << endl, exit( 0 );
}

void solve(){
  ll diff = fib[ N ] - M;
  int ans = 0;
  int k = 1;
  while( fib[ k - 1 ] + fib[ k ] <= diff )
    ++k;
  k = min( k, N - 2 );
  while( diff ){
    ++ans;
    diff -= fib[ k-- ];
    while( fib[ k ] > diff )
      --k;
  }
  cout << ans << endl;
}