1. 程式人生 > >洛谷 P1939 【模板】矩陣加速(數列):優化遞推式的方法——矩陣快速冪

洛谷 P1939 【模板】矩陣加速(數列):優化遞推式的方法——矩陣快速冪

在大多數情況下,O(n)的效率都是值得驕傲的,然而,有時候並不是,比如如何在一秒鐘內算出一個遞推式的第1e9項,很明顯O(n)不行了。

然而常數級又不太現實,除非你的數學非常好,這題又比較簡單,你推了一個特徵方程的通項公式……

所以考慮log的做法:矩陣快速冪

如果你還不知道矩陣快速冪是什麼,請走這邊:傳送門

對於這道題,嗯,模板嘛,已經告訴你了式子,就只需要考慮矩陣了,對於整個過程,我們只需要兩個矩陣,初始矩陣和轉置矩陣:

先看這個特徵方程F[i] = F[i - 1] + F[i - 3],那麼就有一個矩陣如下


我們的目標矩陣就是


那麼,針對這個矩陣我們如何轉置呢?

先看目標矩陣第一個:F[i]

F[i] = F[i - 1] + F[i - 3]

那麼,由矩陣乘法,轉置矩陣第一行,似乎就定了:1 0 1

同樣的,二三行就是1 0 0 和 0 1 0

整個矩陣如下:

程式碼:

#include <bits/stdc++.h>
const int mod = 1000000007;
int n;
inline int read()
{
    int ch;int x = 0;int f = 1;ch=getchar();
    while (ch!='-'&&(ch<'0'||ch>'9'))
        ch=getchar();
    ch=='-'?f=-1:x=ch-'0',ch=getchar();
     while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
     return x*f;
}
struct Mat {
    long long mat[3][3];
};
Mat operator * (Mat a, Mat b) {
    Mat c;
    memset(c.mat, 0, sizeof(c.mat));
    for(int i = 0; i < 3; i++) {
        for(int k = 0; k < 3; k++) {
            for(int j = 0; j < 3; j++) {
                c.mat[i][j] += (a.mat[i][k] % mod) * (b.mat[k][j] % mod) % mod;
                c.mat[i][j] %= mod;
            }
        }
    }
    return c;
}
Mat operator ^ (Mat a, long long k)  {
    Mat c;
    for(int i = 0; i < 3; i++)
        for(int j = 0; j < 3; j++) c.mat[i][j] = (i == j);
    while(k) {
        if(k & 1) c = c * a;
        a = a * a;
        k >>= 1;
    }
    return c;
}
Mat init;
Mat fi;
int t;
signed main()
{
    scanf("%d", &t);
    while(t--)
    {
        memset(init.mat, 0, sizeof(init.mat));
        memset(fi.mat, 0, sizeof(fi.mat));
        scanf("%d", &n);
        init.mat[0][0] = 1;
        init.mat[0][2] = 1;
        init.mat[1][0] = 1;
        init.mat[2][1] = 1;
        fi.mat[0][0] = 1;
        fi.mat[1][0] = 1;
        fi.mat[2][0] = 1;
        if(n <= 3) printf("%d\n", 1);
        else{
            init = init ^ (n - 3);
            fi = init * fi;
            printf("%d\n", fi.mat[0][0]);
        }
    }
    return 0;
}