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
i−1 列全部摆满,然后第
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;
}