P5303 题解

题意简述

T 次询问。每次给出一个正整数 n(1n2×109),表示有 n12×1 的砖和 2 块 1×1 的砖,求在两块 1×1 的砖没有边相邻的前提下,用所有砖铺 2×n 的路面的方案种数。

题目分析

注意到 1n2×109,考虑用矩阵快速幂优化 DP。

我们很自然地想到使用 Fk 表示 n=k 时的种数。考虑状态转移方程。

显然,n=k+1n=k 只在边界多了一列。将铺满这一列的形式分类讨论:

  1. 使用一块竖着的 2×1 的砖。

t

显然铺满的种类就是左边 2×(n1) 区域铺满的种数,即 Fn1

  1. 使用两块横着的 2×1 的砖。

r

铺满的种类就是左边 2×(n2) 区域铺满的种数,即 Fn2

  1. 使用 1×1 的砖。

ss

很明显这块砖可以摆在上下两种位置。以摆在上面为例,那么由于两块 1×1 的砖不能相邻,下面只能使用一块横着的 2×1 的砖;同样这块 1×1 的砖的左侧只能使用一块横着的 2×1 的砖。而再往左也是同样,横着的 2×1 的砖上下位置交替,摆到某块时使用另一块 1×1 的砖。

注意到使用另一块 1×1 的砖之后,这块砖的右侧部分是固定的(只有 1 种摆法),因此铺的种数是只使用 1×2 的砖铺满左侧区域的种数。

Gi 代表用 i1×2 的砖铺满 2×i 的路面的种数。

经过上文类似的讨论可以得到G 的递推公式 G0=1,G1=1,Gi=Gi1+Gi2

回到原题,由于 2×1 的砖摆 k 块时左侧区域为 2×(nk1),种数也就是 Gnk1。再考虑到第 1 块砖有两种摆放方式,因此第 3 种情况的种数就是 2k=2n1Gnk1=2k=0n3Gk=2Sn3,其中 Sn3 代表 Gi 从第 0 项到第 n3 项之和。

综上,有递推公式 Fn=Fn1+Fn2+2Sn3,设计答案矩阵为 [FnFn1Sn2Gn2Gn3],其初始值 为[F3=2F2=0S1=2G1=1G0=1] 。为了将答案矩阵拓展到 [Fn+1FnSn1Gn1Gn2] 转移矩阵为:

[1100010000201000011100110]

我们对转移矩阵一列一列地来解释。

对于第一列,有 Fn+1=1Fn+1Fn1+2Sn2+0Gn2+0Gn3

对于第二列,有 Fn=1Fn+0Fn1+0Sn2+0Gn2+0Gn3

对于第三列,有 Sn1=Sn2+Gn1=0Fn+0Fn1+1Sn2+1Gn2+1Gn3

对于第四列,有 Gn1=0Fn+0Fn1+0Sn2+1Gn2+1Gn3

对于第五列,有 Gn2=0Fn+0Fn1+0Sn2+1Gn2+0Gn3

现在我们只需要计算出转移矩阵的 n3 次幂,再乘到答案矩阵上就好了。

需要注意的是要特判 n2,否则会挂掉。

分析一下时间复杂度。一次矩阵乘法是 O(53)=O(125),因此矩阵快速幂的时间复杂度为 O(125logn),总复杂度为 O(125Tlogn),在 T500,1n2×109,时限 1.00s 的情况下是可以通过的。

代码实现

#include<bits/stdc++.h>
using namespace std;
const int mod=1000000007;
int t,n;
struct matrix
{
int n,m;
int a[6][6];
void init(int k)
{
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(i!=j)
a[i][j]=0;
else
a[i][j]=k;
}//初始化矩阵,k=0是零矩阵,k=1为单位矩阵
friend matrix operator *(matrix a,matrix b)
{
matrix c;
c.n=a.n,c.m=b.m;
c.init(0);
for(int i=1;i<=c.n;i++)
for(int j=1;j<=c.m;j++)
for(int k=1;k<=b.n;k++)
(c.a[i][j]+=1ll*a.a[i][k]*b.a[k][j]%mod)%=mod;
return c;
}//矩阵乘法
friend matrix operator ^(matrix a,int b)
{
matrix c;
c.n=c.m=a.n;
c.init(1);
for(;b;b>>=1)
{
if(b&1)
c=c*a;
a=a*a;
}
return c;
}//矩阵快速幂
}fs,tr;
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
if(n<=2)//特判
{
printf("0\n");
continue;
}
fs.n=1,fs.m=tr.n=tr.m=5;
fs.init(0);
fs.a[1][5]=fs.a[1][4]=1;
fs.a[1][1]=fs.a[1][3]=2;//答案矩阵初始化
tr.init(0);
tr.a[1][1]=tr.a[2][1]=tr.a[1][2]=tr.a[3][3]=tr.a[4][3]=tr.a[4][4]=tr.a[4][5]=tr.a[5][3]=tr.a[5][4]=1;
tr.a[3][1]=2;//转移矩阵初始化
tr=tr^(n-3);
fs=fs*tr;
printf("%d\n",fs.a[1][1]);
}
return 0;
}
posted @   Hadtsti  阅读(4)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示