看到 \(N\leq 2\times 10^9\) 的範圍,一眼矩陣快速冪最佳化 DP。
首先考慮樸素 DP 怎麼寫。根據題目所給資訊,我們設 \(dp_{i,0}\) 表示前面 \(i\) 個方磚,並且已經使用了 \(2\) 個 \(1\times 1\) 的方磚,\(dp_{i,1}\) 則表示前面 \(i\) 個方磚,沒有使用任何一個 \(1\times 1\) 的方磚。
為了轉移至 \(dp_{i,0}\),既然已經使用過了 \(2\) 個 \(1\times 1\) 的方磚,那麼我們剩下的都是 \(1\times 2\) 的方磚,對於這樣的方磚,我們有兩種可行的擺放,一種是直接豎著放,可以由 \(dp_{i-1,0}\) 轉移得到;另一種是兩個橫著放,可以由 \(dp_{i-2,0}\) 得到。而我們還可以在 \([1,i-3]\) 選擇一個 \(j\),然後利用後面的區間 \([j+1,i]\) 放置兩個 \(1\times 1\) 的方磚,且兩個方磚放置的位置分別在第 \(j+1\) 列和第 \(i\) 列。而我們不難發現有兩種情況,一種是這個區間長度為奇數,另一種是區間長度為偶數。
對於奇數長度的區間,我們可以讓兩個方磚放在不同的行,這裡各自的行剩下的長度就是偶數(奇數減一是偶數),我們就每行橫著放 \(1\times 2\) 的方磚就可以了。而如果放在同一行,那麼我們就無法填滿裡面的空缺(可以自己畫圖理解一下)。對於偶數長度的區間,我們可以讓兩個方磚在同一行,這樣上下兩行剩餘的長度就都是偶數了,就可以橫著放 \(1\times 2\) 的方磚來填滿空缺,反之如果是在不同的行,那麼我們就不可以完成。
如果實在理解不了,我可以給一點提示,就是當區間兩邊填了 \(1\times 1\) 的方磚後,那麼對於所填方磚的另外一行,我們只可能使用 \(1\times 2\) 的方磚填滿那個 \(1\times 1\) 的空缺,而此時又會產生新的 \(1\times 1\) 的空缺……以此類推。(這樣提示還不夠,那我可能就幫不了你了。。)
總之我們可以發現,不管是放在不同的行或者相同的行,都會得到 \(2\) 種答案。而且此時我們轉移的是 \(dp_{j,1}\),因為如果在 \([j+1,i]\) 放置兩個裂開的磚塊,那麼 \([1,j]\) 肯定就是沒有裂開的磚塊的。
而我們的 \(dp_{i,1}\) 就非常好轉移了,就是要麼豎著放,要麼兩個橫著放,即 \(dp_{i,1}=dp_{i-1,1}+dp_{i-2,1}\)。
所以我們列出方程:
但是這樣肯定是會超時的,所以就會想到使用矩陣。
但是我們又發現這個 \(dp_{i,0}\) 的轉移不是 \(O(1)\) 的,所以考慮將方程進行改造。對於 \(\sum _{j=1}^{i-3}dp_{j,1}\),我們不難發現每次 \(i\) 增加 \(1\),我們原來的值就會增加一個 \(dp_{j,1}\) 的值。根據這個形式,我們就想到使用字首和儲存這個區間值。設 \(sum_i\) 表示 \(\sum_{j=1}^i dp_{j,1}\) 的值。原方程變為:
我們將一次轉移使用到的值提出來:\(dp_{i-1,0}\),\(dp_{i-2,0}\),\(sum_{i-3}\),\(dp_{i-1,1}\),\(dp_{i-2,1}\)。然後利用這些值構造初始矩陣:
換成具體的數值,真正的初始矩陣是:
注意這個 \(sum_0\) 是 \(1\),因為放置 \(0\) 個方磚也是一種答案。
每次轉移按照上述方程進行轉移,得到轉移矩陣:
所以得到答案為:
這樣程式碼就非常簡單了:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MOD=1e9+7;
long long n,p,q;
void mul(long long f[5],long long a[5][5])
{
long long c[5];
memset(c,0,sizeof(c));
for(int j=0;j<5;j++)
{
for(int k=0;k<5;k++) c[j]=(c[j]+f[k]*a[k][j])%MOD;
}
memcpy(f,c,sizeof(c));
}
void mulself(long long a[5][5])
{
long long c[5][5];
memset(c,0,sizeof(c));
for(int i=0;i<5;i++)
{
for(int j=0;j<5;j++)
{
for(int k=0;k<5;k++) c[i][j]=(c[i][j]+(long long)a[i][k]*a[k][j])%MOD;
}
}
memcpy(a,c,sizeof(c));
}
signed main()
{
long long T;
cin>>T;
while(T--)
{
cin>>n;
long long f[5]={0,0,2,1,1};
long long a[5][5]={0};
a[0][0]=a[0][1]=a[1][0]=a[2][2]=a[2][3]=a[3][2]=a[3][4]=a[4][4]=1,a[4][0]=2;
if(n==1||n==2||!n)
{
puts("0");
continue;
}
n-=2;
while(n)
{
if(n&1) mul(f,a);
mulself(a);
n>>=1;
}
cout<<f[0]<<endl;
}
return 0;
}