把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷5303】[GXOI/GZOI2019] 逼死强迫症(矩乘)

点此看题面

  • 要用\(n\)\(2\times 1\)的方砖去铺一条\(2\times n\)的路。
  • 其中有一块方砖裂成了两块\(1\times 1\)的方砖,问有多少种铺路的方案使得裂成的两块方砖没有相邻边。
  • 数据组数\(\le500,n\le2\times10^9\)

\(1\times1\)方砖两侧的填法

假设铺一条\(2\times i\)的路的方案数为\(f_i\)

考虑对于第一列第一个格子,放在它上面的方砖只有两种方式——竖着或是横着。

  • 如果竖着,那么接下来相当于要铺一条\(2\times(i-1)\)的路,所以\(f_i\)可以由\(f_{i-1}\)转移。
  • 如果横着,考虑这样一来第一列第二个格子也必须横着,相当于把前两列都填完了,因此接下来要铺\(2\times(i-2)\)的路,所以\(f_i\)可以由\(f_{i-2}\)转移。

综上所述,\(f_i=f_{i-1}+f_{i-2}\),也就是说它是斐波那契数!

加上\(1\times 1\)方砖后的填法

考虑我们要铺一条\(2\times i\)的路,强制\(1\times 1\)的方砖分别在路的两侧。

如果\(i\le2\),显然无解。

否则,发现当\(i\)为奇数时,两个方砖只能一个左上一个右下,或是一个左下一个右上,且中间的方砖只有唯一的填法;而当\(i\)为偶数时,两个方砖只能同时在上或同时在下,中间的方砖也只有唯一的填法。

所以说我们设加上\(1\times1\)的方砖后铺一条\(2\times i\)的路的方案数为\(F_i\)

依旧考虑第一列的两个格子,那么首先第一种情况就是它可以不放\(1\times 1\)的方砖,那么转移和之前类似,就是从\(F_{i-1}+F_{i-2}\)转移。

还有一种情况就是它放了\(1\times 1\)的方砖,它有两种摆放方式,因此可以从\(2\sum_{j=0}^{i-3}f_j\)转移(注意是\(f\),因为只能填一次\(1\times1\)的方砖)。

\(\sum_{j=0}^{i-3}f_j\)可以直接维护,当然更好的方式是看出这就等于\(f_{i-1}-1\)(证明考虑归纳,首先\(f_0=f_2-1\),那么每次给\(\sum_{j=0}^{i-3}f_j=f_{i-1}-1\)两侧同时加上\(f_{i-2}\)即可得到\(\sum_{j=0}^{i-2}f_j=f_{i-2}+f_{i-1}-1=f_i-1\))。

\(n\)这么大显然用矩阵加速,只要维护\(F_i,F_{i-1},f_i,f_{i-1},2\)五个值即可。

代码:\(O(Tlogn)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define X 1000000007
using namespace std;
int n;struct M
{
	int a[5][5];I M() {memset(a,0,sizeof(a));}
	I int* operator [] (CI x) {return a[x];}I Con int* operator [] (CI x) Con {return a[x];}
	I M operator * (Con M& o) Con {M t;RI i,j,k;for(i=0;i^5;++i)//矩阵乘法
		for(j=0;j^5;++j) for(k=0;k^5;++k) t[i][j]=(1LL*a[i][k]*o[k][j]+t[i][j])%X;return t;}
	I M operator ^ (RI y) Con {M x=*this,t;for(RI i=0;i^5;++i) t[i][i]=1;W(y) y&1&&(t=t*x,0),x=x*x,y>>=1;return t;}//矩阵快速幂
}S,U,G;
int main()
{
	S[2][2]=S[3][3]=1,S[4][4]=2,U[0][0]=U[0][1]=U[1][0]=U[2][2]=U[2][3]=U[3][2]=U[4][4]=1,U[2][0]=2,U[4][0]=X-1;//初始化初始矩阵和转移矩阵
	RI Tt;scanf("%d",&Tt);W(Tt--) scanf("%d",&n),G=S*(U^(n-1)),printf("%d\n",(0LL+G[2][0]+G[3][0]+G[4][0])%X);return 0;//每次直接矩阵快速幂
}

posted @ 2021-03-27 06:24  TheLostWeak  阅读(57)  评论(0编辑  收藏  举报