前置需求:DP,矩阵快速幂
Part 0
DP的核心思想就是“层”的定义。而本题有一个较为明显的方向,就是本题中的层。对于一个烟囱,我们可以一层一层的搭。对于每一层,我们知道了这层有那几个地方已经被竖着摆放的砖头放置后,就可枚举推出放满这层后的下一层的状态。而每层的状态都可以状态压缩。
Part 1
举个栗子
原始状态
(首先,这个烟囱是从下向上搭的,以下省略号表示放在本层的砖块,省略号方向表示砖块放置的方向,0表示本层这个位置没有砖块,U表示这个位置是一个横跨本层与上一层的砖块,D表示这个位置是一个横跨本层与下一层的砖块)
[
0
0
0
0
0
0
0
0
0
]
\left[ \begin{matrix} 0 & 0 & 0\\ 0 & 0 & 0\\ 0 & 0 & 0\\ \end{matrix} \right]
⎣⎡000000000⎦⎤
随便进行一次操作后本层状态为。
[
⋮
⋯
⋯
⋮
0
U
U
⋯
⋯
]
\left[ \begin{matrix} \vdots & \cdots & \cdots \\ \vdots & 0 & U \\ U & \cdots & \cdots \\ \end{matrix} \right]
⎣⎢⎢⎡⋮⋮U⋯0⋯⋯U⋯⎦⎥⎥⎤
那么上一层的状态就是这样子。
[
0
0
0
0
0
D
D
0
0
]
\left[ \begin{matrix} 0 & 0 & 0 \\ 0 & 0 & D \\ D& 0 & 0\\ \end{matrix} \right]
⎣⎡00D0000D0⎦⎤
以上操作可以通过DFS完成,这样就实现了状态的转移(由于每层状态的每个位置都只有0和D这两种符号,故用二进制装压即可。最后的答案就是搭到第n+1层时第n+1层全为0的状态。然后可以因此处理出矩阵,再用矩阵快速幂。
Part 3
如果直接存2^9个状态,是会TLE的。
这里因为中间的那一个位置肯定没有砖块,故不用存。此时每层有28个状态,又因为每一层的状态肯定只有砖块的数量为奇数时才合法(这个还是蛮好理解的)。故可以吧这28个状态映射为2^7个状态。
若追求速度可以将转移矩阵处理之后直接赋值而不是在程序中处理。
AC代码及部分解释:
#include<cstdio>
#include<cstring>
#define P 1000000007
#define M 258
int ID[M];
struct Matrix{//矩阵快速幂板子
int n,m;
int num[M][M];
void resize(int x,int y){n=x,m=y;}
void Init(){for(int i=0;i<n;i++)for(int j=0;j<m;j++)num[i][j]=i==j;}
void clear(){memset(num,0,sizeof(num));}
Matrix operator *(const Matrix &x)const{
Matrix res;
res.resize(n,x.m);
for(int i=0;i<res.n;i++){
for(int j=0;j<res.m;j++){
res.num[i][j]=0;
for(int k=0;k<m;k++)res.num[i][j]=(res.num[i][j]+1ll*num[i][k]*x.num[k][j]%P)%P;
}
}
return res;
}
void Print(){
for(int i=0;i<n;i++){
for(int j=0;j<m;j++)printf("%d ",num[i][j]);
puts("");
}
}
}A,B,S;
Matrix Mul(Matrix a,int b){
Matrix res;
res.resize(a.n,a.m);
res.Init();
while(b){
if(b&1)res=res*a;
a=a*a;
b>>=1;
}
return res;
}
int Pc;//当前的初始状态
int F(int x){return x-(x>=4);}
void dfs(int now,int step,int nxt){//now当时本层的状态,step当前操作哪块砖块,nxt当前上一层的状态
if(step==4){
dfs(now,step+1,nxt);
return;
}
if(step==9){
B.num[ID[nxt]][ID[Pc]]++;//注意这个要和初始的状态算,而不是当前本层的状态
return;
}
if((1<<F(step))&now){//把step转换为他在二进制里的位置(也就是去掉中间那一个位置的砖块后它的位置)
dfs(now,step+1,nxt);
return;
}
dfs(now|(1<<F(step)),step+1,nxt|(1<<F(step)));//这个位置竖着放
if(step!=3&&step%3!=2&&((now&(1<<F(step+1)))==0))dfs(now|(1<<F(step))|(1<<F(step+1)),step+1,nxt);//放-字形
if(step!=1&&step<=5&&((now&(1<<F(step+3)))==0))dfs(now|(1<<F(step))|(1<<F(step+3)),step+1,nxt);//放1字形
}
int Count(int x){//计算这个二进制数有几位
int cnt=0;
for(int i=x;i>0;i-=i&-i)cnt++;
return cnt;
}
void Init(){
int I=1<<8,id=-1;
for(int i=0;i<I;i++){
if(Count(i)%2==1)continue;
ID[i]=++id;//把状态映射一下,减少状态数量
}
A.resize(1,id+1);//id为当前的状态为0~id,故有id+1个状态
A.clear();
A.num[0][0]=1;
B.resize(id+1,id+1);
B.clear();
for(int i=0;i<I;i++){
if(Count(i)%2==1)continue;
Pc=i;
dfs(i,0,0);
}
}
int main(){
Init();
int T;
scanf("%d",&T);
for(int Case=1;Case<=T;Case++){
int n;
scanf("%d",&n);
S=A*Mul(B,n);//向上搭n层
printf("Case %d: %d\n",Case,S.num[0][0]);
}
return 0;
}