给出一张矩形表格,一些地方有障碍物,向其中放1*2的多米诺骨牌(不用填满),要求任何相邻两行之间都有至少一个骨牌横跨,任何相邻两列之间也都至少有一个骨牌横跨,求方案数。
为方便,用S(i,j,p,q)表示左上角为(i,j),右上角为(p,q)的子矩形
首先用dp预处理出dp[i][j][p][q]表示S(i,j,p,q)随意放骨牌的方案数(不考虑行列有无横跨),然后可以发现dp[i][j][u][q]*dp[i][u+1][p][q]表示在S(i,j,p,q)中第u列和第u+1列之间没有横跨骨牌的个数。
因此我们可以用容斥解决列横跨,而对于行横跨则不能这样求解(复杂度更高),我们可以用f[i]数组表示前i行任何相邻两行之间都有至少一个骨牌横跨的方案数,这样就可以得出:
f[x]=t[1][x]- ∑ i = 2 x \sum_{i=2}^x ∑i=2xf[i-1]*t[i][x]
其中t[i][j]表示i行到j行不考虑行横跨的方案数。
将两种横跨的求法结合起来,即可得到答案。
#include
#include
#define ll long long
#define N 20
#define MN 40000
#define M 19901013
using namespace std;
ll m,n,dp[N][N][N][N],tmp[2][MN],f[N],num[N],top,ans;
char str[N];
bool mm[N][N];
inline ll ask(ll u,ll v)
{
return (u>>v)&1;
}
inline void work(ll w,ll u,ll v)
{
ll i,j,k,l,t,t2,zt,tot=(1 << (v-u+1))-1;
bool now=0,nxt;
for(i=0; i<=tot; i++) tmp[0][i]=0;
t2=0;for(i=u;i<=v;i++) if(mm[w][i]) t2|=(1 << (i-u));
tmp[0][t2]=1;
for(i=w+1; i<=m+1; i++)
{
zt=0;
for(j=u,t=0; j<=v; j++,t++)
{
zt|=(mm[i][j] << t);
nxt=now^1;
for(k=0; k<=tot; k++) tmp[nxt][k]=0;
for(k=0; k<=tot; k++)
{
if(!tmp[now][k]) continue;
t2=k;
if(ask(t2,t)!=mm[i][j]) t2^=(1 << t);
tmp[nxt][t2]=(tmp[nxt][t2]+tmp[now][k])%M;
if(ask(k,t)) continue;
if(j!=v&&!ask(k,t+1))
{
t2=k|(1 << (t+1));
if(ask(t2,t)!=mm[i][j]) t2^=(1 << t);
tmp[nxt][t2]=(tmp[nxt][t2]+tmp[now][k])%M;
}
if(!mm[i][j])
{
t2=k|(1 << t);
tmp[nxt][t2]=(tmp[nxt][t2]+tmp[now][k])%M;
}
}
now=nxt;
}
dp[w][u][i-1][v]=tmp[now][zt];
}
}
int main()
{
ll i,j,k,tot,p,q,t,u,d;
cin>>m>>n;
for(i=1; i<=m; i++)
{
scanf("%s",str+1);
for(j=1; j<=n; j++)
{
mm[i][j]=(str[j]!='.');
}
}
for(i=1; i<=n; i++)
{
for(j=i; j<=n; j++)
{
for(k=1; k<=m; k++)
{
work(k,i,j);
}
}
}
tot=(1 << (n-1))-1;
for(i=0; i<=tot; i++)
{
top=0;
num[++top]=0;
for(j=0; j<n-1; j++)
{
if(ask(i,j)) num[++top]=j+1;
}
num[++top]=n;
for(d=1; d<=m; d++)
{
for(u=1; u<=d; u++)
{
t=1;
for(j=2; j<=top; j++)
{
p=num[j-1]+1,q=num[j];
t=t*dp[u][p][d][q]%M;
}
if(u==1) f[d]=t;
else f[d]-=t*f[u-1]%M,f[d]%=M;
}
}
f[m]=(f[m]+M)%M;
if(top&1) ans-=f[m];
else ans+=f[m];
ans%=M;
}
cout<<(ans+M)%M;
}