[GXOI/GZOI2019] 逼死強迫症 題解

Supor__Shoop發表於2024-09-27

看到 \(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}\)

所以我們列出方程:

\[\begin{cases} dp_{i,0}=dp_{i-1,0}+dp_{i-2,0}+2\times \sum _{j=1}^{i-3}dp_{j,1}\\ dp_{i,1}=dp_{i-1,1}+dp_{i-2,1} \\\end{cases} \]

但是這樣肯定是會超時的,所以就會想到使用矩陣

但是我們又發現這個 \(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,0}=dp_{i-1,0}+dp_{i-2,0}+2\times sum_{i-3} \]

我們將一次轉移使用到的值提出來:\(dp_{i-1,0}\)\(dp_{i-2,0}\)\(sum_{i-3}\)\(dp_{i-1,1}\)\(dp_{i-2,1}\)。然後利用這些值構造初始矩陣:

\[\left( \begin{matrix} dp_{i-1,0} & dp_{i-2,0} & dp_{i-1,1} & dp_{i-2,1} & sum_{i-3} \end{matrix} \right) \]

換成具體的數值,真正的初始矩陣是:

\[\left( \begin{matrix} 0 & 0 & 2 & 1 & 1 \end{matrix} \right) \]

注意這個 \(sum_0\)\(1\),因為放置 \(0\) 個方磚也是一種答案。

每次轉移按照上述方程進行轉移,得到轉移矩陣:

\[\left( \begin{matrix} 1 & 1 & 0 & 0 & 0\\ 1 & 0 & 0 & 0 & 0\\ 0 & 0 & 1 & 1 & 0\\ 0 & 0 & 1 & 0 & 1\\ 2 & 0 & 0 & 0 & 1 \end{matrix} \right) \]

所以得到答案為:

\[\left( \begin{matrix} 0 & 0 & 2 & 1 & 1 \end{matrix} \right) \times \left( \begin{matrix} 1 & 1 & 0 & 0 & 0\\ 1 & 0 & 0 & 0 & 0\\ 0 & 0 & 1 & 1 & 0\\ 0 & 0 & 1 & 0 & 1\\ 2 & 0 & 0 & 0 & 1 \end{matrix} \right) ^{n-2} \]

這樣程式碼就非常簡單了:

#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;
}

相關文章