1. 程式人生 > >Luogu P3873 [TJOI2010]天氣預報 題解

Luogu P3873 [TJOI2010]天氣預報 題解

main code inline std 優秀 esp baidu int www.

  • 這題輸入數據好坑啊。。

    本題解是給像我一樣的蒟蒻寫的,可能略顯啰嗦,已經懂了的大佬可以出門右轉,去切掉IOI

  • 我們先分析題目,將每一個\(w_i\),它的計算方式寫成如下方式:
  • \(w_i=a_1*w_{i-1}+a_2*w_{i-2}+\cdots+a_n*w_{i-n}\)
  • 再註意到題中的這句話:

    a1, a2, ..., an是已知常數

  • 所以這是一個常系數的線性遞推方程。很自然的,對於這種方程,我們可以想到直接\(O(nm)\)遞推(每個數都要乘n次)。
  • 繼續看題。這時候我們驚訝的發現了下面這個東西:

    \(1\leq n\leq 100,n < m\leq 10000000\)

  • 很好,跑滿的時間復雜度為\(O(10^9)\)
    ,這絕對不會是這道題的正解不然怎麽會是藍題呀
  • 這怎麽辦呢?看起來好像沒有什麽優秀的辦法來優化啊怎麽可能,沒有怎麽做題

    這時候,就該我們神奇的矩陣快速冪(或稱矩陣加速)出場了!

    矩陣快速冪

  • 前置知識:矩陣乘法、快速冪。
  • 數學姿勢:
    • 1.矩陣滿足結合律
    • 2.矩陣不滿足交換律(這就要求了我們進行快速冪運算的時候必須要註意乘的順序)
  • 由以上兩點我們就可以得出一個很好用的結論:當一個矩陣乘上\(k\)次另一個矩陣的時候,我們可以看做是乘上這個矩陣的\(k\)次方,而矩陣可以像數一樣用二進制的方式進行快速冪運算。
  • 由此我們就推出了矩陣快速冪的基本原理。

    我們先來舉個栗子:

  • 斐波那契數列大家應該都很熟悉吧?它的遞推式如下:\(F_n=F_{i-1}+F_{i-2}\)
  • 在一定的範圍以內,很顯然可以\(O(n)\)遞推的,但是n很大呢?
  • 我們可以考慮矩陣快速冪。
  • 相信很多同學們都知道斐波那契數列的轉移矩陣是這個形式的:
    \(\left[ \begin{matrix} F_n\\ F_{n-1}\\ \end{matrix} \right]=\)\(\left[ \begin{matrix} 1 & 1\\ 1 & 0\\ \end{matrix} \right]^{n-2}*\)\(\left[ \begin{matrix} F_2\\ F_1\\ \end{matrix} \right]\)
  • 但是為什麽是這樣的可能有些人卻不太了解,這裏給出解釋。
  • 我們考慮兩個矩陣相乘:
    \(\left[\begin{matrix} a & b\\ c & d\\ \end{matrix} \right]=\)\(\left[\begin{matrix} a_1 & b_1\\ c_1 & d_1\\ \end{matrix} \right]*\)\(\left[\begin{matrix} a_2 & b_2\\ c_2 & d_2\\ \end{matrix} \right]\)
  • 由矩陣乘法的定義我們可以知道:\(a=a_1*a_2+b_1*c_2\)
  • 我們將上方矩陣補齊為\(2*2\)的:

\(\left[ \begin{matrix} F_n & 0\\ F_{n-1} & 0\\ \end{matrix} \right]=\)\(\left[ \begin{matrix} 1 & 1\\ 1 & 0\\ \end{matrix} \right]^{n-2}*\)\(\left[ \begin{matrix} F_2 & 0\\ F_1 & 0\\ \end{matrix} \right]\)

  • 那麽當\(n=3\)時,我們有:
  • \(F_3=1*F_2+1*F_1=2\)
  • \(F_2=1*F_2+0*F_1=1\)
  • 繼續向下推,我們驚奇的發現,對每一個新得到的表示\(F_n\)的矩陣,都是由這兩個式子推過來的:
  • \(F_n=1*F_{n-1}+1*F_{n-2}\)
  • \(F_{n-1}=1*F_{n-1}+0*F_{n-2}\)
  • 我們發現它很神奇的符合了斐波那契數列的遞推式,但是為什麽?
  • 假設我們有形式這樣一個等式:

\(\left[\begin{matrix} F_n\\ F_{n-1}\\ \vdots \\ F_{n-m+1}\\ \end{matrix} \right]=\)\(\left[\begin{matrix} a_1 & a_2 & \cdots & a_m\\ b_1 & b_2 & \cdots & b_m\\ \vdots & \vdots & \vdots & \vdots\\ k_1 & k_2 & \cdots & k_m\\ \end{matrix} \right]*\)\(\left[\begin{matrix} F_{n-1}\\ F_{n-2}\\ \vdots \\ F_{n-m}\\ \end{matrix} \right]\)

  • 我們進行類似的分析可以發現:對於每一個左邊的目標矩陣中的\(F_i\),它都滿足這樣一個形式:(\(j\)代表\(F_i\)這個數所對應的矩陣中的那一行)
  • \(F_i=F_{n-1}*j_1+F_{n-2}*j_3+\dots+F_{n-m}*j_m\)
  • 因為斐波那契數列只和後兩位有關,所以可以用一個\(2*2\)的矩陣來表示轉移過程中的\(bas\)矩陣(中間那個),然後將\(F_i\)\(F_{i-1}\)寫成上面最左邊的一列,而\(F_{i-1}\)\(F_{i-2}\)寫成最右邊的然後依次對應著將系數填入\(bas\),就得到了這個斐波那契數列的矩陣形式的轉移方程。

    好了矩陣快速冪的介紹差不多就到這裏,當然,聽機房大佬說二維的好像也可以????這裏就不管了因為我不會啊,我們來說一下這道題。

  • 我們看最開始給出的那個遞推式,我們將它寫成矩陣形式:
    \(\left[\begin{matrix} W_i\\ W_{i-1}\\ W_{i-2}\\ \vdots \\ W_{i-n+1}\\ \end{matrix} \right]=\)\(\left[\begin{matrix} a_1 & a_2 & a_3 & \cdots & a_n\\ 1 & 0 & 0 & \cdots & 0\\ 0 & 1 & 0 & \cdots & 0\\ \vdots & \vdots & \vdots & \vdots\\ 0 & 0 & \cdots & 1 & 0\\ \end{matrix} \right]*\)\(\left[\begin{matrix} W_{i-1}\\ W_{i-2}\\ W_{i-3}\\ \vdots \\ W_{i-n}\\ \end{matrix} \right]\)
  • 因為我們要求第\(m\)天,所以我們需要在基礎矩陣的基礎上乘上\(bas\)矩陣的\(m-n\)次方(因為要推這麽多次)。狀態轉移如下:
    \(\left[\begin{matrix} W_m\\ W_{m-1}\\ W_{m-2}\\ \vdots \\ W_{m-n+1}\\ \end{matrix} \right]=\)\(\left[\begin{matrix} a_1 & a_2 & a_3 & \cdots & a_n\\ 1 & 0 & 0 & \cdots & 0\\ 0 & 1 & 0 & \cdots & 0\\ \vdots & \vdots & \vdots & \vdots\\ 0 & 0 & \cdots & 1 & 0\\ \end{matrix} \right]^{m-n}*\)\(\left[\begin{matrix} W_n\\ W_{n-1}\\ W_{n-2}\\ \vdots \\ W_1\\ \end{matrix} \right]\)
  • 準備工作已經做好了(DP的題基本也就一個推方程吧。。),剩下的就是記板子上代碼了。

    AC Code:

#include<bits/stdc++.h>
#define del(a,i) memset(a,i,sizeof(a))
#define ll long long
#define inl inline
#define il inl void
#define it inl int
#define ill inl ll
#define re register
#define ri re int
#define rl re ll
#define mid ((l+r)>>1)
#define lowbit(x) (x&(-x))
#define INF 0x3f3f3f3f
#define mod 4147
using namespace std;
template<class T>il read(T &x)
{
    int f=1;char k=getchar();x=0;
    for(;k>'9'||k<'0';k=getchar()) if(k=='-') f=-1;
    for(;k>='0'&&k<='9';k=getchar()) x=(x<<3)+(x<<1)+k-'0';
    x*=f;
}
const int MAXN = 105;
int n,m,w[MAXN],a[MAXN];
struct Matrix{    //矩陣快速冪模板
    int val[MAXN][MAXN];
    Matrix(){del(val,0);}
    int *operator [](int x){return val[x];}
    Matrix operator *(Matrix t){
        Matrix res;
        for(ri i=1;i<=n;i++)
            for(ri j=1;j<=n;j++)
                for(ri k=1;k<=n;k++)
                    res[i][j]=(res[i][j]+val[i][k]*t[k][j])%mod;
        return res;
    }
}ans,bas;
il init(){     //準備工作:構建矩陣
    for(ri i=1;i<=n;i++) ans[i][1]=w[n-i+1];
    for(ri i=1;i<=n;i++) bas[1][i]=a[i];
    for(ri i=2;i<=n;i++) bas[i][i-1]=1;
}
it qpow(int w){   //形式和快速冪一毛一樣
    while(w){
        if(w&1) ans=bas*ans;//這裏要註意,就像前面說的,矩陣不具有交換性,一定是bas在前!!這一點很重要
        bas=bas*bas;w>>=1;
    }
    return ans[1][1];
}
int main()
{
//  freopen(".in","r",stdin);
//  freopen(".out","w",stdout);
    read(n),read(m);
    for(ri i=n;i;i--) read(w[i]);
    for(ri i=1;i<=n;i++) read(a[i]);
    init();
    printf("%d\n",qpow(m-n));
    return 0;
}

Luogu P3873 [TJOI2010]天氣預報 題解