poj 3420 百炼 3483 Quad Tiling:图解分析+详细题解

题目大意

4*N的方格,你用 2 ∗ 1 2*1 21的矩形来填充,有多少种填充方式。

思路分析

从简到难依次进行分析
f ( k ) f(k) f(k) n = k n=k n=k时的排列总数, f ( 0 ) = 1 f(0)=1 f(0)=1

  • n = 1 n=1 n=1的时候,只有一种排列,再乘以一行之前的排列数,即 f ( 1 ) = 1 ∗ f ( 0 ) f(1)=1*f(0) f(1)=1f(0)
    在这里插入图片描述

  • n = 2 n=2 n=2的时候,首先考虑第二列是上图的情况,有 f ( 2 ) = 1 ∗ f ( 1 ) f(2)=1*f(1) f(2)=1f(1),再考虑两行并在一起的情况,有四种情况,也就是说 f ( 2 ) = 1 ∗ f ( 1 ) + 4 ∗ f ( 0 ) = 5 f(2)=1*f(1)+4*f(0)=5 f(2)=1f(1)+4f(0)=5
    在这里插入图片描述

  • n = 3 n=3 n=3的时候,最后一列单独拿出来有1种情况 1 ∗ f ( 2 ) 1*f(2) 1f(2),最后两列单独拿出来有4种情况, 4 ∗ f ( 1 ) 4*f(1) 4f(1),那么三列单独拿出来呢?有两种,也就是说 f ( 3 ) = 1 ∗ f ( 2 ) + 4 ∗ f ( 1 ) + 2 ∗ f ( 0 ) = 11 f(3)=1*f(2)+4*f(1)+2*f(0)=11 f(3)=1f(2)+4f(1)+2f(0)=11

在这里插入图片描述
注意右图四个不算在三列单独拿出来的种类中。对于第三个图而言,他的最后一列依然可以单独拿出来。
在这里插入图片描述

  • n = 4 n=4 n=4的时候,仍然具有四列的不可拆分的组合,你不能把下图拆成任意的1-3的组合的组合。所以 f ( 4 ) = 1 ∗ f ( 3 ) + 4 ∗ f ( 2 ) + 2 ∗ f ( 1 ) + 3 ∗ f ( 0 ) = 36 f(4)=1*f(3)+4*f(2)+2*f(1)+3*f(0)=36 f(4)=1f(3)+4f(2)+2f(1)+3f(0)=36
    在这里插入图片描述

  • n = 5 n=5 n=5,将5列单独拿出来,又有两种情况
    在这里插入图片描述

  • n > 4 n> 4 n>4,比如5列的时候,四列的任何排列都可以分解成1-3列其中几种的组合,因此不独立。 f ( 5 ) = f ( 4 ) + 4 f ( 3 ) + 2 f ( 2 ) + 3 f ( 1 ) + 2 f ( 0 ) = 95 f(5)=f(4)+4f(3)+2f(2)+3f(1)+2f(0)=95 f(5)=f(4)+4f(3)+2f(2)+3f(1)+2f(0)=95

到这里我们来分析一下这么些个情况,后三项呈现出2,3,2的系数,这是一个巧合吗,不妨回去看看3列不可拆分的情况,5列不可拆分的情况,你会发现5列不可拆分的情况,就是在三列的基础上进行了填补,所以5列单独拿出来和三列单独拿出来的组合数一样,同样的,如果再次对6进行推导,你会发现6是4的填充,于是我们得到如下的结论。
在这里插入图片描述
a [ i ] a[i] a[i]表示单独i列的组合数目,那么
a [ 0 ] = a [ 1 ] = 1 , a [ 2 ] = 4 , a [ 2 ∗ k ] = 3 , a [ 2 ∗ k + 1 ] = 2 a[0]=a[1]=1,a[2]=4,a[2*k]=3,a[2*k+1]=2 a[0]=a[1]=1,a[2]=4,a[2k]=3,a[2k+1]=2
于是我们就得到了递推关系式
f ( n ) = ∑ i = n − 1 0 a [ i ] f ( i ) f(n)=\sum_{i=n-1}^{0}{a[i]f(i)} f(n)=i=n10a[i]f(i)
此时我们几行代码就可以搞定这道题 ,如果 N N N在100之内,我们只需要几行代码就可以搞定这道题。

	a[0] = a[1] = 1; a[2] = 4;
	for (int i = 3; i < 100; i++) a[i] = (i % 2) == 0 ? 3 : 2;
	f[0] = 1;
	for (int i = 1; i < 105; i++) {
		for (int j = i - 1; j >= 0; j--) {
			f[i] += a[i - j] * f[j];
		}
	}

转身一看 1 ≤ N ≤ 1 0 9 1 ≤ N ≤ 10^9 1N109,显然我们没有这么大的数组。那么怎么办呢?不妨找一下 f ( n ) f(n) f(n) f ( n − 1 ) f(n-1) f(n1)的关系,尽量计算 f ( n ) f(n) f(n)时不需要太多的位数
f ( n ) = f ( n − 1 ) + 4 f ( n − 2 ) + 2 f ( n − 3 ) + 3 f ( n − 4 ) + . . . f(n)=f(n-1)+4f(n-2)+2f(n-3)+3f(n-4)+... f(n)=f(n1)+4f(n2)+2f(n3)+3f(n4)+...
f ( n − 1 ) = f ( n − 2 ) + 4 f ( n − 3 ) + 2 f ( n − 4 ) + 3 f ( n − 5 ) + . . . f(n-1)=f(n-2)+4f(n-3)+2f(n-4)+3f(n-5)+... f(n1)=f(n2)+4f(n3)+2f(n4)+3f(n5)+...二者相加
f ( n ) = 5 f ( n − 2 ) + 6 f ( n − 3 ) + 5 ( f ( n − 4 ) + f ( n − 5 ) + . . . ) f(n)=5f(n-2)+6f(n-3)+5(f(n-4)+f(n-5)+...) f(n)=5f(n2)+6f(n3)+5(f(n4)+f(n5)+...)那么
f ( n − 1 ) = 5 f ( n − 3 ) + 6 f ( n − 4 ) + 5 ( . . . ) f(n-1)=5f(n-3)+6f(n-4)+5(...) f(n1)=5f(n3)+6f(n4)+5(...)二者相减
f ( n ) = f ( n − 1 ) + 5 f ( n − 2 ) + f ( n − 3 ) − f ( n − 4 ) f(n)=f(n-1)+5f(n-2)+f(n-3)-f(n-4) f(n)=f(n1)+5f(n2)+f(n3)f(n4)这样
那我们是不是可以将数组滚动一下

#include<iostream>
using namespace std;
#define MAX 10000000001,
int f[10];
int main() {
	int n = 0, m = 0;
	while (cin >> n >> m && n + m > 0) {
		f[0] = 1; f[1] = 1; f[2] = 5; f[3] = 11; f[4] = 36;
		for (int i = 5; i <= n; i++) {
			int i1 = i % 10, i2 = (i - 1) % 10, i3 = (i - 2) % 10, i4 = (i - 3) % 10, i5 = (i - 4) % 10;
			f[i1] = (f[i2] + (5 * f[i3]) % m + f[i4] - f[i5]) % m;
		}
		cout << f[n] << endl;
	}
}

没有MLE!但是TLE,数据范围还是太大 ,我们无法避免每次循环都要跑一次完整的程序(因为内存不够),如何优化?再从公式入手,联想一下斐波那契数列我们构造矩阵进行运算,这里也是可以的。
f ( n ) = f ( n − 1 ) + 5 f ( n − 2 ) + f ( n − 3 ) − f ( n − 4 ) f(n)=f(n-1)+5f(n-2)+f(n-3)-f(n-4) f(n)=f(n1)+5f(n2)+f(n3)f(n4)
五个变量值,我们取前四个作为目标值,后四个作为自变量,
在这里插入图片描述
于是目标就变成了如何构造矩阵A,经过观察我们可以得到,A应该是
在这里插入图片描述
于是乎我们得到了,
在这里插入图片描述
初始化
在这里插入图片描述
只需要求出 C = A n C=A^n C=An,然后输出 C [ 0 ] ∗ B C[0]*B C[0]B即可

还有一些小问题需要注意,错了一中午,在代码里标注了。(百炼AC了,结果Poj CR,他的编译器总给出不太支持数组用{}初始化,懒得改了)

#include<iostream>
using namespace std;

#define MAX 10000000001,
int n, m;
struct matrix {
	int a[4][4] = { {1,5,1,-1},{1,0,0,0},{0,1,0,0} ,{0,0,1,0} };
	void init1() { a[0][0] = a[1][1] = a[2][2] = a[3][3] = 1; }
	void init0() { for (int i = 0; i < 4; i++)for (int j = 0; j < 4; j++)a[i][j] = 0; }
	void operator = (const matrix & m) {
		for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) a[i][j] = m.a[i][j];
	}
};

matrix mul(matrix a, matrix b) {
	matrix res; res.init0();
	for (int i = 0; i < 4; i++)
		for (int j = 0; j < 4; j++)
			for (int k = 0; k < 4; k++)
				res.a[i][j] = (res.a[i][j] + (a.a[i][k] * b.a[k][j]) % m) % m;
	return res;
}

//a^p
matrix quickPow(matrix a, int p) {
	matrix res; res.init0(); res.init1();//(1)对角矩阵!谨记这个初始化
	while (p > 0) {
		if (p & 1) res = mul(res, a);
		a = mul(a, a);
		p >>= 1;
	}
	return res;
}

int B[4][1] = { 11,5,1,1 };
int main() {
	while (cin >> n >> m && n + m > 0) {
		if (n <= 3) {//(2)特判,不要忘了取余
			cout << B[3 - n][0] % m << endl; continue;
		}
		matrix tmp;
		matrix A = quickPow(tmp, n - 3);//(3)注意是n-3次方
		int res = 0;
		for (int i = 0; i < 4; i++)res = (res + (A.a[0][i] * B[i][0]) % m) % m;
		cout << (res + m) % m << endl;//(4)res值可能为负,需要进行操作
	}
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值