习题:Xenia and Dominoes(状压DP&容斥)
题目
思路
首先我们考虑如果只有\(x\)和\(.\)该如何去做
设\(dp[i][j]\)表示前i列,第i列的状态为j,前i-1列均被填满的方案总数
先考虑横着放
对于\(dp[i][j]\)这个状态,那么必须先将其中的0填满,再先外转移,同时要保证骨牌是横着放对\(dp[i+1][j']\)是合法的
再考虑竖着放
其实就是看是否有连续的0出现
通过这个DP就可以求出方案总数
这个题目中保证空位只有1个
我们考虑,如果要保证空位的合法性,那么至少有一个骨牌的短的那一面是朝向空位的
考虑一个容斥,至少有i个骨牌是朝着空位,我们考虑选i个方向,在这些位置上强制不准放,再跑DP
注意到空位上也要保证不可以放
代码
#include<iostream>
#include<cstring>
using namespace std;
const int mod=1e9+7;
int m;
int x,y;
char a[4][10005];
int b[4][10005],vis[(1<<4)],nxt[10005];
int dx[5]={0,0,1,-1};
int dy[5]={1,-1,0,0};
long long dp[10005][(1<<3)],ans;
bool inside(int x,int y)
{
if(0<=x&&x<=2&&1<=y&&y<=m)
return 1;
return 0;
}
int init(int s)
{
memset(nxt,0,sizeof(nxt));
for(int i=0;i<=2;i++)
{
for(int j=1;j<=m;j++)
{
if(a[i][j]=='.')
b[i][j]=0;
if(a[i][j]=='O'||a[i][j]=='X')
b[i][j]=1;
}
}
for(int i=0;i<=3;i++)
{
if(s&(1<<i))
{
int tx=x+dx[i]*2;
int ty=y+dy[i]*2;
if(inside(tx,ty)==0)
s=s-(1<<i);
else if(b[x+dx[i]][y+dy[i]]==1||b[x+dx[i]*2][y+dy[i]*2]==1)
s=s-(1<<i);
else
{
for(int j=1;j<=2;j++)
b[x+dx[i]*j][y+dy[i]*j]=1;
}
}
}
for(int i=1;i<=m;i++)
for(int j=0;j<3;j++)
if(b[j][i])
nxt[i]=nxt[i]|(1<<j);
return s;
}
int count_two(int val)
{
int ret=0;
while(val)
{
ret=ret+(val&1);
val>>=1;
}
return ret;
}
long long solve()
{
memset(dp,0,sizeof(dp));
dp[1][nxt[1]]=1;
if((nxt[1]&(1<<0))==0&&(nxt[1]&(1<<1))==0)
dp[1][nxt[1]|(1<<0)|(1<<1)]=1;
if((nxt[1]&(1<<1))==0&&(nxt[1]&(1<<2))==0)
dp[1][nxt[1]|(1<<1)|(1<<2)]=1;
for(int i=2;i<=m;i++)
{
for(int j=nxt[i-1];j<(1<<3);j=(j+1)|nxt[i-1])
{
if((j^((1<<3)-1))&nxt[i])
continue;
dp[i][(j^((1<<3)-1))|nxt[i]]=(dp[i][(j^((1<<3)-1))|nxt[i]]+dp[i-1][j])%mod;
}
if((nxt[i]&(1<<0))==0&&(nxt[i]&(1<<1))==0)
{
dp[i][3]=(dp[i][3]+dp[i][0])%mod;
dp[i][7]=(dp[i][7]+dp[i][4])%mod;
}
if((nxt[i]&(1<<1))==0&&(nxt[i]&(1<<2))==0)
{
dp[i][6]=(dp[i][6]+dp[i][0])%mod;
dp[i][7]=(dp[i][7]+dp[i][1])%mod;
}
}
return dp[m][(1<<3)-1];
}
int main()
{
cin>>m;
for(int i=0;i<=2;i++)
for(int j=1;j<=m;j++)
{
cin>>a[i][j];
if(a[i][j]=='O')
{
x=i;
y=j;
}
}
for(int i=1;i<(1<<4);i++)
{
int now=init(i);
if(vis[now]||now==0)
continue;
ans=(ans+solve()*(count_two(now)%2?1:-1))%mod;
vis[now]=1;
}
cout<<(ans+mod)%mod;
return 0;
}

浙公网安备 33010602011771号