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;
}
浙公网安备 33010602011771号