JZOJ3473. 铺砖问题

题目大意

求用\(1*2\)的砖铺满\(n*m\)的方案数.
\(\text{Task 1}\) : \(n<=100, m<=11\)
\(\text{Task 2}\) : \(n<=10^{200}, m<=5\)

解题思路

啊, 这题很像很像「显示器」呢.. 居然没有做出来.
我太菜了! 居然同类型的题都做不出来, 之后有这种题做不出来倒立......

考虑\(m<=11\)的部分分, 简单思考可得简单的状压dp....然后说正解, 正解是暴力的优化.
等等, 不要删部分分, 因为正解只能处理\(m<=5\)的情况, 要分Task讨论.(其实貌似也可以正解, 但我懒)

把「这个位置是竖着的砖块的上部分」的看做\(1\), 其他的看做\(0\), 如此状压后, 由于每一行没有本质上的区别, 多了一行不过是多了一次转移, 容易(真的吗)想到可以通过简单的矩阵乘法代表一次转移.

由于此题比「显示器」要简单一点, 我就细讲一下矩阵乘法代表一次dp转移的原理.
通过矩阵乘法的定义式:若\(C=A*B\), 则有$$C_{i,j}=\sum_kA_{i,k}*B_{k,j}$$
那么可以把\(A_{i,k}*B_{k,j}\)看作是\(i\)状态转移到\(k\)状态,又由\(k\)状态再转移到\(j\)状态的方案数.
由于两次转移一定会有一个中间量, 所以\(\sum\)一下就是通过两次转移, 从\(i\)状态转移到\(j\)状态的总方案数.

显然如果我们构造出一开始的转移一次的矩阵\(P\), \(P^n_{0,0}\)就是答案, 因为一开始的状态和最终的状态都是\(0\).

问题在于, 如何构造初始矩阵\(P\). 其实这也不是问题, 因为可以通过简单的dfs来构造出来, 具体地, 要遵循如下规则:

对于一个位置i:
1.上一行是1的, 这一行必须是0.(相当于拼上去了竖着的砖块的下部分.)
2.上一行是0的:
	1).这一行可以填0, 条件是i<m并且上一行的i+1位是0(相当于填了一个横着的砖.)
	2).这一行可以填1, 没有条件.(相当于填了一个竖着的砖块的上部分.)

时间复杂度:\(O(((2^5)^2+200)\log{10^{200}})\)(加上了高精除二的复杂度)
code:(半考场代码写得丑请见谅!)

#include <cstdio>
#include <cstring>
#define M 32
#define MOD 1000000007
#define init(a, b) memset(a, b, sizeof(a))
#define fo(i, a, b) for(int i = (a); i <= (b); ++i)
#define fd(i, a, b) for(int i = (a); i >= (b); --i)
using namespace std;
inline int read()
{
	int x = 0; char ch = getchar();
	while(ch < '0' || ch > '9')	ch = getchar();
	while(ch >= '0' && ch <= '9')	x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar();
	return x;
}
int m, p2[10] = {1};
char st[220];
struct Mat
{
	int a[M + 5][M + 5];
	Mat(){init(a, 0);}
	int* operator[](const int x){return a[x];}
	friend Mat operator*(Mat a, Mat b)
	{
		Mat c;
		fo(i, 0, M)
			fo(k, 0, M)
			{
				if(!a[i][k])	continue ;
				register long long x = a[i][k];
				fo(j, 0, M)
					if(b[k][j])
						c[i][j] = (c[i][j] + 1ll * x * b[k][j]) % MOD;
			}
		return c;
	}
}p, ans;
struct Longint
{
	int n, a[210];
	Longint(){n = 1; init(a, 0);}
	int& operator[](const int x){return a[x];}
	void div2()
	{
		int tmp = 0, cur;
		fd(i, n, 1)
			cur = a[i] & 1, a[i] = (a[i] + tmp * 10) >> 1, tmp = cur;
		while(!a[n] && n)	--n;
	}
}n;
void trans(int S, int T, int t)
{
	if(t > m)	return (void)(++p[S][T], 0);
	if(T & p2[t - 1])	trans(S, T, t + 1);
	else
	{
		if(t < m && !(T & p2[t]))	trans(S, T, t + 2);
		trans(S | p2[t - 1], T, t + 1);
	}
}
namespace Task1
{
	const int N = 110, W = 12;
	int n, m, ans, p2[W] = {1}, f[N][1 << W];
	inline void add(int &a, int b){a += b; a >= MOD && (a -= MOD);}
	int S;
	inline void set0(int w){(S & p2[w - 1]) && (S ^= p2[w - 1]);}
	inline void trans(int r, int T, int t)
	{
		if(t > m)	return add(f[r][T], f[r - 1][S]);
		if(T & p2[t - 1])	set0(t), trans(r, T, t + 1);
		else
		{
			if(t < m && !(T & p2[t]))	set0(t), set0(t + 1), trans(r, T, t + 2);
			S |= p2[t - 1], trans(r, T, t + 1);
		}
	}
	int main()
	{
		fo(i, 1, m)	p2[i] = p2[i - 1] << 1;
		int full = p2[m] - 1;
		f[0][0] = 1;
		fo(i, 1, n)
			fo(j, 0, full)
				trans(i, j, 1);
		printf("%d\n", f[n][0]);
		return 0;
	}
}
int main()
{
	scanf("%s", st + 1); scanf("%d", &m);
	n.n = strlen(st + 1);
	fo(i, 1, n.n)	n[i] = st[n.n - i + 1] ^ 48;
	fo(i, 1, m)	p2[i] = p2[i - 1] << 1;
	fo(i, 0, m)	ans[i][i] = 1;
	if(m > 5)
	{
		Task1::n = n[1] + n[2] * 10 + n[3] * 100;
		Task1::m = m;
		Task1::main();
		return 0;
	}
	int full = p2[m] - 1;
	fo(i, 0, full)	trans(0, i, 1);
	for(; n.n; n.div2(), p = p * p)
		(n[1] & 1) && (ans = ans * p, 1);
	printf("%d\n", ans[0][0]);
	return 0;
}
posted @ 2020-12-12 16:35  Martin_MHT  阅读(101)  评论(0)    收藏  举报