問題はこちら
No.320 眠れない夜に - yukicoder
正しいフィボナッチ数列をとする(indexはとなるよう取る)
に対してはとおく(漸化式に従って添字を負に拡張した定義とは異なることに注意)
厳密に証明しようとすると死ぬほどめんどくさいので答えだけ先に行っておくと
「なら-1、さもなくば和がになるようにを大きい方から貪欲に取った時の個数」となる
いくつか実験してみると直感的には明らかなのだけど、数学的に厳密に証明しようとすると大仰になってしまった。
===証明===
計算間違いでは真値より小さくしかならないことから、ならそのような間違え方は存在しない
とする。
適当な整数をとり、数列を
で定める(つまりaは第k項の計算を間違え、それ以外は正しく計算した数列)
このときに対し
であり
なのでとなる。
k,m(k<m)の2回で計算を間違えた場合は、その数列をbとし、aを上と同じく取れば同様の議論により となるので
となる。
帰納的に、第項の計算で間違えた場合、第i項は になる
よって問題は「をの相異なる元の和として表す事ができるか、できるならそのために必要な元の個数は最小でいくつか?」となる
(仮定よりである)
・存在
Nに関する帰納法で示せる。
のとき明らか
のとき、なら帰納法の仮定より成立。そうでないならをとればなので帰納法の仮定から成立。
・最小性
大きい方から貪欲にとった時に個数が最小になることを示す
なる最大のkを取る。(仮定より)
ゲース1:のとき
なので*1必ずを使う
ケース2:のとき
のとき明らか。とする
αを、を使わずにの相異なるものの和で表すのが最小であると仮定する
このとき、なので*2、そのような組み合わせは必ず隣接する2項を含む
そのなかで最大のものをであるとすると、xの取り方からは使われていないので、この2項をに取る変えることができる。
仮定よりなので取り替えた後の組み合わせも条件を満たし、最小性に反する。
===証明終===
(余談)
解説で触れられているゼッケンドルフの定理 - Wikipediaは多分あんまり関係ないと思う。これは「任意の数は隣接しないフィボナッチ数の和として一意に表せる」であり、今回の問題に対応しそうな形に書きなおせば「はの隣接しない~~」であり、も使える点が大きく異る。これを使わないようにするには、例えば「があればにバラす」などの方法があるが、元の表現にが含まれていた時には~~などと考えると、結局は帰納法で示すしか無く、だったら別にゼッケンドルフの定理使わなくても…という感じ。
(余談終わり)
なのでフィボナッチ数列の計算は64bitで大丈夫
int main(){ long m,f[90]; int n,i,c=0; f[1]=f[2]=1; for(i=3;i<=80;i++)f[i]=f[i-1]+f[i-2]; scanf("%d%ld",&n,&m); m=f[n]-m; if(m<0){puts("-1");return 0;} for(i=n-2;m;i--)if(m>=f[i]){m-=f[i];c++;} printf("%d",c); return 0; }
ささっと縮めて
long m,f[99]; s; main(n){ for(f[1]=1;++n<90;f[n]=f[n-1]+f[n-2]); scanf("%d%ld",&n,&m); for(m=f[n--]-m;--n;m/f[n]?m-=f[n],s++:0); s=!printf("%d",m?-1:s); }
f[1]=1っていうのが邪魔っぽいので頑張って睨むと
for(f[1]=1;++n<90;f[n]=f[n-1]+f[n-2]); for(;--n+90;f[1-n]=f[-n]+f[~n]+!n);
とできるので(f[-1]を読んでいる)
long m,f[99]; s; main(n){ for(;--n+90;f[1-n]=f[-n]+f[~n]+!n); scanf("%d%ld",&n,&m); for(m=f[n--]-m;--n;m/f[n]?m-=f[n],s++:0); s=!printf("%d",m?-1:s); }
144B