Quad Tiling

Quad Tiling

题目链接:POJ 3420

题目大意

现有一个 4 × n 4\times n 4×n 的棋盘,问用 2 × 1 2\times 1 2×1 的多米诺骨牌将其完美覆盖的方法有多少种。

思路

这道题是状态压缩加矩阵乘法。

我们可以看到只有 4 4 4 列,那我们可以考虑状态压缩。
然后我们想一想普通dp怎么搞: f i , j f_{i,j} fi,j 为前 i − 1 i-1 i1 列全部摆满,然后第 i i i 列摆的情况是 j j j 有多少种方案。
然后就从每一个可以到的地方转移过来。

然后你就会发现它其实就是一个 16 × 16 16\times 16 16×16 的矩阵。

然后这个矩阵乘 n n n 次,左上角的值就是答案。

这个矩阵是这样的:

1 0 0 1 0 0 0 0 0 1 0 0 1 0 0 1
0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0
0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0
1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0
0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0
0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0
0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0
0 1 0 0 1 0 0 1 0 0 0 0 0 0 0 0
1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0
0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0
0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0
1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

至于这个矩阵怎么得到,两种方法,在代码里面讲。

代码

手推矩阵法

就是根据自己推理,一个一个求出来。
(或者就是打表)
反正我一开始就是这样做的。

#include<cstdio>
#include<cstring>
#define ll long long

using namespace std;

struct matrix {
	int n, m;
	ll a[21][21];
}a, b, re;
int n, m;
matrix operator *(matrix x, matrix y) {
	re.n = x.n;
	re.m = y.m;
	for (int i = 0; i < re.n; i++)
		for (int j = 0; j < re.m; j++)
			re.a[i][j] = 0;
	
	for (int k = 0; k < x.m; k++)
		for (int i = 0; i < re.n; i++)
			for (int j = 0; j < re.m; j++)
				re.a[i][j] = (re.a[i][j] + (x.a[i][k] * y.a[k][j]) % m) % m;
	
	return re;
}

void jzksm(int now) {
	b = a;
	now--;
	while (now) {
		if (now & 1) b = b * a;
		a = a * a;
		now /= 2;
	}
} 

int main() {
	scanf("%d %d", &n, &m);
	while (n || m) {
		a.n = 16;
		a.m = 16;
		memset(a.a, 0, sizeof(a.a));
		a.a[0][0] = 1 % m;a.a[3][0] = 1 % m;a.a[9][0] = 1 % m;a.a[12][0] = 1 % m;a.a[15][0] = 1 % m;
		a.a[2][1] = 1 % m;a.a[8][1] = 1 % m;a.a[14][1] = 1 % m;
		a.a[1][2] = 1 % m;a.a[13][2] = 1 % m;
		a.a[0][3] = 1 % m;a.a[12][3] = 1 % m;
		a.a[8][4] = 1 % m;a.a[11][4] = 1 % m;
		a.a[10][5] = 1 % m;
		a.a[9][6] = 1 % m;
		a.a[8][7] = 1 % m;
		a.a[1][8] = 1 % m;a.a[4][8] = 1 % m;a.a[7][8] = 1 % m;
		a.a[0][9] = 1 % m;a.a[6][9] = 1 % m;
		a.a[5][10] = 1 % m;
		a.a[4][11] = 1 % m;
		a.a[0][12] = 1 % m;a.a[3][12] = 1 % m;
		a.a[2][13] = 1 % m;
		a.a[1][14] = 1 % m;
		a.a[0][15] = 1 % m;
		
		jzksm(n);
		printf("%lld\n", b.a[0][0]);
		
		scanf("%d %d", &n, &m);
	}
	
	return 0;
} 

dfs求矩阵法

dfs 来求是打完表才想到的一个做法。

我们枚举列,然后维护现在是从多少号转移到多少号

就是要么横着放,要么竖着放。
竖着放就是至少要有两列,然后之前也要横着放。
如果竖着放,那要么就是凸出来,要么就没有凸出来。

#include<cstdio>
#include<cstring>
#define ll long long

using namespace std;

struct matrix {
	int n, m;
	ll a[21][21];
}a, b, re;
int n, m;
matrix operator *(matrix x, matrix y) {
	re.n = x.n;
	re.m = y.m;
	for (int i = 0; i < re.n; i++)
		for (int j = 0; j < re.m; j++)
			re.a[i][j] = 0;
	
	for (int k = 0; k < x.m; k++)
		for (int i = 0; i < re.n; i++)
			for (int j = 0; j < re.m; j++)
				re.a[i][j] = (re.a[i][j] + (x.a[i][k] * y.a[k][j]) % m) % m;
	
	return re;
}

void jzksm(int now) {
	b = a;
	now--;
	while (now) {
		if (now & 1) b = b * a;
		a = a * a;
		now /= 2;
	}
} 

void dfs(int now, int pre, int nxt) {
	if (now == 4) {
		a.a[pre][nxt] = (a.a[pre][nxt] + 1) % m;
		return ;
	}
	
	dfs(now + 1, (pre << 1), (nxt << 1) + 1);//这个位置放一个横的
	dfs(now + 1, (pre << 1) + 1, (nxt << 1));//之前的位置已经放了横的
	if (now <= 2) dfs(now + 2, pre << 2, nxt << 2);//放一个纵的
}

int main() {
	scanf("%d %d", &n, &m);
	while (n || m) {
		a.n = 16;
		a.m = 16;
		memset(a.a, 0, sizeof(a.a));
		dfs(0, 0, 0);
		
		jzksm(n);
		printf("%lld\n", b.a[0][0]);
		
		scanf("%d %d", &n, &m);
	}
	
	return 0;
} 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值