bzoj 2669 题解(状压dp+搜索+容斥原理)

时间:2022-01-03 19:30:57

这题太难了...看了30篇题解才整明白到底咋回事...

核心思想:状压dp+搜索+容斥

首先我们分析一下,对于一个4*7的棋盘,低点的个数至多只有8个(可以数一数)

这样的话,我们可以进行一个状压,把所有的低点压进来

然后我们从小到大枚举所有数,转移即可

记状态f[i][j]表示到了第i个数,低点的状态为j的方案数

那么在转移的时候,有两个转移方向:

①.如果第i个数放在低点上,那么我们可以枚举所有的低点k,如果低点没有在状态里,有:

dp[i][j|(1<<k)]+=dp[i-1][j]

②.如果第i个数放在高点上,那么我们需要枚举所有可以使用的高点,所谓可以使用的高点就是指的某一高点周围没有未使用的低点(原因:我是从小往大枚举的所有数,如果我在一个高点上放了一个数而他附近却有低点没有放上,那么这个低点会变得比这个高点高,这就是不合法的了。)

但是如果每次都枚举,时间复杂度是会爆炸的,所以我们需要进行一个预处理num[i],表示i状态下有多少个高点可以使用

最后的答案就是dp[n*m][1<<cnt-1]

可是这个答案是正确的吗?

我们发现,如果仅仅是这么枚举,很容易出现一种情况:某个点本来是高点,但是由于随意的放置使得这个点变成了低点

所以我们需要解决掉这个问题

怎么解决?

很显然,我们只需求出把每个可能被放错的高点真正作为低点的方案数,然后用总方案数减掉这个方案数就可以了。

可是,如果我们分别去减,由于减的方案数还要像上面那样dp出来,所以会产生更多的重复(即高点1和高点2同时放错)

所以我们需要利用容斥,即

0个高点反转-1个高点反转+2个高点反转....

(反转指反转状态)

那么怎么反转?

dfs!

贴代码:

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#define ll long long
#define mode 772002
using namespace std;
bool used[10][10];
bool maps[10][10];
int n,m;
ll dp[30][(1<<15)+5];
char ch[10];
int dir[10][2]={{0,0},{1,1},{1,0},{1,-1},{0,1},{0,-1},{-1,1},{-1,0},{-1,-1}};
int temp[30][2];
int num[(1<<15)+5];
ll ans=0;
bool check(int x,int y)
{
if(x>0&&x<=n&&y>0&&y<=m)
{
return 1;
}
return 0;
}
ll get_dp()
{
int cnt=0;
memset(dp,0,sizeof(dp));
memset(used,0,sizeof(used));
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(maps[i][j])
{
used[i][j]=1;
temp[++cnt][0]=i;
temp[cnt][1]=j;
}
}
}
for(int i=0;i<=(1<<cnt)-1;i++)
{
int tot=0;
memset(used,0,sizeof(used));
for(int j=1;j<=cnt;j++)
{
if(!((1<<(j-1))&i))
{
if(!used[temp[j][0]][temp[j][1]])
{
used[temp[j][0]][temp[j][1]]=1;
tot++;
}
for(int t=1;t<=8;t++)
{
int x=temp[j][0]+dir[t][0];
int y=temp[j][1]+dir[t][1];
if(check(x,y)&&!used[x][y])
{
used[x][y]=1;
tot++;
}
}
}
}
num[i]=n*m-tot;
}
dp[0][0]=1;
for(int i=1;i<=n*m;i++)
{
for(int j=0;j<=(1<<cnt)-1;j++)
{
dp[i][j]+=dp[i-1][j]*max(num[j]-i+1,0)%mode;
dp[i][j]%=mode;
for(int k=1;k<=cnt;k++)
{
if(!((1<<(k-1))&j))
{
dp[i][j|(1<<(k-1))]+=dp[i-1][j];
dp[i][j|(1<<(k-1))]%=mode;
}
}
}
}
return dp[n*m][(1<<cnt)-1];
}
void dfs(int x,int y,int typ)
{
if(x==n+1)
{
if(typ%2)
{
ans-=get_dp();
ans%=mode;
}else
{
ans+=get_dp();
ans%=mode;
}
return;
}
if(y==m+1)
{
dfs(x+1,1,typ);
return;
}
dfs(x,y+1,typ);
if(!maps[x][y])
{
bool flag=0;
for(int i=1;i<=8;i++)
{
int st=x+dir[i][0];
int ed=y+dir[i][1];
if(maps[st][ed])
{
flag=1;
break;
}
}
if(!flag)
{
maps[x][y]=1;
dfs(x,y+1,typ+1);
maps[x][y]=0;
return;
}
}
}
int main()
{
int cot=0;
while(scanf("%d%d",&n,&m)!=EOF)
{
cot++;
bool u=0;
memset(maps,0,sizeof(maps));
for(int i=1;i<=n;i++)
{
scanf("%s",ch);
for(int j=0;j<m;j++)
{
if(ch[j]=='.')
{
maps[i][j+1]=0;
}else
{
maps[i][j+1]=1;
if(maps[i-1][j+1]||maps[i][j])
{
u=1;
}
}
}
}
if(u)
{
printf("Case #%d: 0\n",cot);
continue;
}
ans=0;
dfs(1,1,0);
printf("Case #%d: %lld\n",cot,(ans%mode+mode)%mode);
}
return 0;
}