题目
思路
将道路看成一个很高很瘦的家伙(一个 n × 2 n\times 2 n×2 的瘦高个)。
考虑最后一行(或者说,第一行)是什么情况。用 f ( n ) f(n) f(n) 表示答案。
- 被一个横着的 1 × 2 1\times2 1×2 砖填满了。方案数 f ( n − 1 ) f(n-1) f(n−1)。
- 被两个竖着的 1 × 2 1\times2 1×2 砖填满了。方案数 f ( n − 2 ) f(n-2) f(n−2)。
- 存在一个 1 × 1 1\times 1 1×1 的小砖块。
不好搞定的是第三种情况。下面都只讨论第三种情况。如图。
- 不妨设第一行的 1 × 1 1\times 1 1×1 砖块在右侧。最开始,放置方法唯一,如图 ( 1 ) (1) (1)。
- 下一步,由于某一行有一个“空洞”,我得将其填补,所以如图 ( 2 ) (2) (2)。
- 反复填补,直到遇到另一个 1 × 1 1\times 1 1×1 的砖块才能够停止。可能是任意一行。如图 ( 3 ) (3) (3)。
这说明,我们只需要唯一确定另一块 1 × 1 1\times 1 1×1 的砖块在哪一行即可。在这两块 1 × 1 1\times 1 1×1 的砖块之间的方案是唯一的。而剩余部分(顶上的空白)是简单情况。
如果用 g ( n ) g(n) g(n) 表示只使用 1 × 2 1\times 2 1×2 的砖块填满 n × 2 n\times 2 n×2 的道路的方案数,那么有递推式
f ( n ) = f ( n − 1 ) + f ( n − 2 ) + 2 ∑ i = 0 n − 3 g ( i ) f(n)=f(n-1)+f(n-2)+2\sum_{i=0}^{n-3}g(i) f(n)=f(n−1)+f(n−2)+2i=0∑n−3g(i)
i i i 的上界是 n − 3 n-3 n−3,因为两个 1 × 1 1\times 1 1×1 砖块至少有 3 3 3行(不信看图 ( 1 ) (1) (1),清晰明了)。还要乘 2 2 2,是因为第一行的 1 × 1 1\times 1 1×1 砖块可以在左,也可以在右。
然后问题是 g ( n ) g(n) g(n)怎么求?然而是一样的分析方法—— g ( n ) = g ( n − 1 ) + g ( n − 2 ) g(n)=g(n-1)+g(n-2) g(n)=g(n−1)+g(n−2)
我们只需要用一个矩乘快速幂优化一下即可。复杂度 O ( q log n ) \mathcal O(q\log n) O(qlogn)(我的常数为 4 3 4^3 43)。
代码
#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
inline int readint(){int x;scanf("%d",&x);return x;}
const int Mod = 1e9+7;
struct Matrix{
int a[5][5];
Matrix(){
for(int i=0; i<5; ++i)
for(int j=0; j<5; ++j)
a[i][j] = 0;
}
static Matrix I(){
Matrix x;
for(int i=0; i<5; ++i)
x.a[i][i] = 1;
return x; /* 单位矩阵 */
}
Matrix operator*(const Matrix &b)const{
Matrix c;
for(int i=0; i<5; ++i)
for(int k=0; k<5; ++k)
for(int j=0; j<5; ++j)
c.a[i][k] = (1ll*a[i][j]*b.a[j][k]%Mod+c.a[i][k])%Mod;
return c;
}
void output()const{
printf("%d\n",a[0][0]);
}
};
inline Matrix qkpow(Matrix x,int q){
Matrix ans = Matrix::I();
for(; q; q>>=1,x=x*x)
if(q&1) ans = ans*x;
return ans;
}
Matrix Q, S; int T;
void input(){
T = readint();
Q.a[0][0] = Q.a[0][1] = 1, Q.a[0][2] = 2;
Q.a[1][0] = 1;
Q.a[2][2] = Q.a[2][3] = 1;
Q.a[3][3] = Q.a[3][4] = 1;
Q.a[4][3] = 1;
S.a[2][0] = 1;
S.a[3][0] = 1;
S.a[4][0] = 1;
}
void solve(){
while(T --){
int n = readint();
if(n <= 2)
puts("0");
else
(qkpow(Q,n-2)*S).output();
}
}
int main(){
freopen("obsession.in","r",stdin);
freopen("obsession.out","w",stdout);
input(), solve();
return 0;
}