题目链接:洛谷 BZOJ
这道题的方法是用dfs进行搜索容斥时,dp统计答案(感觉痛苦的同学建议做做)
一个局部最小值就可以覆盖最少四个点(放在四个角上),最多九个(放在中间)
所以最多只会有8个局部最小值,可以考虑一下状压
我们可以考虑从小到大地放入每个元素
dp[i][j]表示在选择的局部最小值状态为j时,已经选了i个数,此时的方案数
这张图里面X代表的是局部最小值,红色的代表已经被选择了
那么思考dp[i][j]应该从哪里转移过来
如果这次选的不是局部最小值,那么思考可以选取哪些位置
所有的局部最小值的位置已经不能放了
还未选取的局部最小值周围的所有位置也不能放
这个可以通过预处理每个状态得出总共有多少个位置,记为cnt[j]
其他的点就都可以选取了,总共有n*m-cnt[j]-(i-1)个
所以转移方程就是dp[i][j]+=dp[i−1][j]×(cnt[j]−i+1);
如果选取的是局部最小值,那么就枚举这次选的是第k个局部最小值
转移方程就是dp[i][j]+=dp[i-1][j^(1<<k)];
然后我们来思考这样一个问题:
我们只保证了这几个一定是局部最小值,外面会不会有其他的局部最小值呢?
所以统计的方案是有多余的
考虑用容斥来解决这个问题
减去外面有一个多余的方案,再加上两个多余的方案,等等...也就是奇加偶减
用dfs来实现这个过程,最多能够搜索到2的8次方个状态,所以复杂度是有保障的
跑得非常快
#include<bits/stdc++.h>
using namespace std;
const int mod=12345678;
int dx[9]={0,-1,-1,-1,0,1,1,1,0};
int dy[9]={1,1,0,-1,-1,-1,0,1,0};
int ans,n,m,posx[10],posy[10],cnt[100005],maxa,vis[5][10];
int dp[30][305];
char a[5][10];
int moc(int x)
{
if(x>=mod) return x-mod;
else if(x<0) return x+mod;
return x;
}
int dpp()
{
memset(dp,0,sizeof(dp));
maxa=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(a[i][j]=='X')
{
posx[++maxa]=i;
posy[maxa]=j;
}
}
}
for(int i=0;i<(1<<maxa);i++)
{
memset(vis,0,sizeof(vis));
cnt[i]=n*m;
for(int j=0;j<maxa;j++)
{
if(!((1<<j)&i))
{
for(int k=0;k<9;k++)
{
int xx=posx[j+1]+dx[k];
int yy=posy[j+1]+dy[k];
if(xx>=1&&xx<=n&&yy>=1&&yy<=m&&!vis[xx][yy])
vis[xx][yy]=1,cnt[i]--;
}
}
}
}
dp[0][0]=1;
for(int i=1;i<=n*m;i++)
{
for(int j=0;j<(1<<maxa);j++)
{
dp[i][j]=1ll*dp[i-1][j]*(max(0,cnt[j]-i+1))%mod;
for(int k=0;k<maxa;k++)
{
if((1<<k)&j)
{
dp[i][j]=moc(dp[i][j]+dp[i-1][j^(1<<k)]);
}
}
}
}
return dp[n*m][(1<<maxa)-1];
}
bool check(int x,int y)
{
for(int i=0;i<9;i++)
if(x+dx[i]>=1&&x+dx[i]<=n&&y+dy[i]>=1&&y+dy[i]<=m&&a[x+dx[i]][y+dy[i]]=='X')
return false;
return true;
}
void dfs(int kind)
{
ans=moc(ans+kind*dpp());
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(check(i,j))
{
a[i][j]='X';
dfs(-kind);
a[i][j]='.';
}
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
scanf("%s",a[i]+1);
}
dfs(1);
cout<<ans;
return 0;
}