AOJ 2372 IkaNumber

解法

問題文を言い換えると,フィボナッチ数同士の積で表される数で K 番目に小さいものは何か?という問題です.
実験すればなんとなく規則性が見えてきます.
自分は fib(n) * fib(m) (1 <= n, m <= 10 ぐらい)の表を出力したらわかりました.
小さい順から斜めにずら~っとならんだ感じの表が得られます.
あとは何番目の斜めの場所にあるか探索して,いい感じにインデックスを合わせるだけです.
コーナーケースはないですが,ちゃんと手で確かめないとインデックスがずれやすいかも.

ソースコード

#include <bits/stdc++.h>
using namespace std;

using ll = long long;
using matrix = vector<vector<ll>>;

constexpr ll M = 1e9 + 7;

matrix mul(matrix a, matrix b) {
    matrix res(a.size(), vector<ll>(b[0].size()));
    for(int i = 0; i < (int)a.size(); ++i) {
        for(int k = 0; k < (int)b.size(); ++k) {
            for(int j = 0; j < (int)b[0].size(); ++j) {
                (res[i][j] += a[i][k] * b[k][j]) %= M;
            }
        }
    }
    return res;
}

matrix modpow(matrix x, ll n) {
    matrix res(x.size(), vector<ll>(x.size()));
    for(int i = 0; i < (int)x.size(); ++i) {
        res[i][i] = 1;
    }
    
    while(n > 0) {
        if(n & 1) {
            res = mul(res, x);
        }
        x = mul(x, x);
        n >>= 1;
    }
    return res;
}

ll fib(int n) {
    matrix A(2, vector<ll>(2));
    A[0][0] = A[0][1] = 1;
    A[1][0] = 1;
    matrix b(2, vector<ll>(1));
    b[0][0] = b[1][0] = 1;
    return mul(modpow(A, n - 1), b)[0][0];
}

int main() {
    ll K;
    cin >> K;

    ll lb = 0, ub = 1e10;
    while(ub - lb > 1) {
        ll m = (lb + ub) / 2;
        if(m * (m + 1) < K) {
            lb = m;
        } else {
            ub = m;
        }
    }
    K -= lb * (lb + 1);
    int x = lb * 2 + (K - 1) / ub + 1;
    if(K > ub) {
        K -= ub;
    }
    int y = 0;
    if(K <= (ub + 1) / 2) {
        y = 2 * K - 1;
    } else {
        y = ub - (ub & 1) - (K - (ub + 1) / 2 - 1) * 2;
    }

    cout << (fib(y) * fib(x - y + 1) % M) << endl;
}

感想

実験は大切だと再認識する問題.

これは一般的な話ですが,闇雲に実験しても(無駄ではないですが)いい知見が得られないことも多くて,実験するのにも経験が必要だなあと感じます.