Mondriaan's Dream
$ solution: $
这道题 $ wch $ 首先想到的是搜索和状态压缩,因为这道题的方块可以竖着也就是产生一个凸起(这里可以状态压缩),我们可以枚举它i行j列上下端凸起的情况产生的方案数,然后进行合并。但是这种方法需要维护的东西太多了,复杂度也很难预测。所以 $ wch $ 想着忽略上端凸起,直接从第一行开始向下递推,然后只需要用状态研所记录下端竖着的(也就是突出的那一部分)。但是递推倒下一行时仍然很麻烦。
于是我们采用一个状态压缩DP惯用的套路:预处理。这样我们预处理处每一行的填充方案(可以不填)(竖着的块默认向下突出,这样就没有上端凸起),然后我们用二进制的与或运算可以完成两个凸起和凹陷(凹陷就是哪一个格子没被填)是否契合或者有重叠。然后我们暴力枚举(可以预见每一行的填充方案不会很多,这也是预处理的一大好处)。然后我们最后一行只需要选那个下端没有突出的方案作为答案输出即可。
$ code: $
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#define ll long long
#define db double
#define mp make_pair
#define rg register int
using namespace std;
int n,m,sx,top;
int tou[2055];
ll f[13][2055];
queue<pair<int,int> > p,q;
struct bian{
int v,to;
}b[4200005];
inline int qr(){
char ch; bool sign=0; rg res=0;
while(!isdigit(ch=getchar()))if(ch=='-')sign=1;
while(isdigit(ch))res=res*10+(ch^48),ch=getchar();
return sign?-res:res;
}
inline void dfs(int x,int v){
if(x>=m){
b[++top].v=v;
b[top].to=tou[sx];
tou[sx]=top;
return ;
}
if(!(sx&(1<<x))){
dfs(x+1,v|(1<<x));
if(!(sx&(1<<(x+1)))&&x<m-1){
dfs(x+2,v);
}
}else dfs(x+1,v);
}
int main(){
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
while((n=qr())&&(m=qr())){
rg nn=1<<m; top=0; q=p;
memset(f,0,sizeof(f)); f[0][0]=1;
memset(tou,0,sizeof(tou));
for(rg i=0;i<nn;++i)
sx=i, dfs(0,0);
q.push(mp(0,0));
while(!q.empty()){
rg x=q.front().first;
rg y=q.front().second+1;
q.pop(); if(y>n)break;
for(rg i=tou[x];i;i=b[i].to){
if(!f[y][b[i].v])q.push(mp(b[i].v,y));
f[y][b[i].v]+=f[y-1][x];
}
}printf("%lld
",f[n][0]);
}
return 0;
}