SRM 687 div1 easy:AlmostFibonacciKnapsack
問題
以下のような漸化式で数列が与えられる。
a[1] = 2
a[2] = 3
a[n] = a[n-1] + a[n-2] - 1
また, 2 以上 10^18 以下の整数 x が与えられる。各値が互いに異なる数列 b1, b2, ... を用いて x = a[b1] + a[b2] + ... + a[bn] としたい。このような {bi} が存在する場合はその数列のうち一つを求めよ。
解法
次のような戦略で x を構成することが出来ます。
- x==0 なら終了
- a[i] <= x < a[i+1] を満たす i を探す
- x==a[i]+1 なら, 数列 b に i-1, i-2 を追加して終了
- それ以外なら, x から a[i] を引いて, 数列 b に i を追加
これで上手く行くのは数学的帰納法で示せます。まず, x = 2, 3, 4 あたりで上の戦略が有効なのは明らかです。
x = 2, 3, ..., a[k]-1 まで上手く行くと仮定します。この時, x = a[k], a[k]+1, ..., a[k+1]-1 まで上手く行くことを示します。
x = a[k] は OK です。また, x-a[k] < a[k+1]-a[k] < a[k-1] より, x > a[k] の時は, b に k を追加して x-a[k] について考えれば上手く行くことがわかります。ただし, x==a[k]+1 の時だけは, 帰納法の仮定にないので, これは別に示さないとダメです。といってもこれも自明で, この時は x = a[k-1] + a[k-2] なので大勝利です。
const ll INF = 1e18+10; class AlmostFibonacciKnapsack { public: vector <int> getIndices(long long x) { vector<int> ans; if (x==1) { ans.push_back(-1); return ans; } vector<ll> arr; arr.push_back(2); arr.push_back(3); while (1) { int n = arr.size(); ll tmp = arr[n-1] + arr[n-2] - 1; arr.push_back(tmp); if (tmp > INF) break; } int n = arr.size(); while (x) { int i = 0; for (; i < n; i++) { if (arr[i] > x) break; } i--; if (arr[i]+1 == x) { ans.push_back(i-1); ans.push_back(i-2); break; } ans.push_back(i); x -= arr[i]; } for (int& el : ans) el++; return ans; } };