[GXOI/GZOI2019]逼死强迫症

题目

传送门 to luogu

思路

将道路看成一个很高很瘦的家伙(一个 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(n1)
  • 被两个竖着的 1 × 2 1\times2 1×2 砖填满了。方案数 f ( n − 2 ) f(n-2) f(n2)
  • 存在一个 1 × 1 1\times 1 1×1 的小砖块。

不好搞定的是第三种情况。下面都只讨论第三种情况。如图。

在这里插入图片描述

  1. 不妨设第一行的 1 × 1 1\times 1 1×1 砖块在右侧。最开始,放置方法唯一,如图 ( 1 ) (1) (1)
  2. 下一步,由于某一行有一个“空洞”,我得将其填补,所以如图 ( 2 ) (2) (2)
  3. 反复填补,直到遇到另一个 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(n1)+f(n2)+2i=0n3g(i)

i i i 的上界是 n − 3 n-3 n3,因为两个 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(n1)+g(n2)

我们只需要用一个矩乘快速幂优化一下即可。复杂度 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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值