【BZOJ2669】區域性極小值(容斥原理+狀壓dp)
阿新 • • 發佈:2019-01-01
題意:有一個行列的整數矩陣,其中到之間的每個整數恰好出現一次。如果一個格子比所有相鄰格子(相鄰是指有公共邊或公共頂點)都小,我們說這個格子是區域性極小值。給出所有區域性極小值的位置,你的任務是判斷有多少個可能的矩陣。
首先,我們會發現最多隻有個區域性極小值(這個不難想,不會的自己想一想),於是我們可以暴力出所有的區域性極小值的情況(保證原本規定的位置必為區域性極小值),然後得到一些區域性極小值的位置,它們的集合為。
然後考慮從到一個一個數填進去。同時用狀壓處理。我們用表示當前狀態為,已經填入到時的方案數。
於是狀態轉移方程:
其中表示狀態除這些區域性極小值及其八相鄰的方格之外還有多少個方格,這個我們可以預處理出來。
求出這些之後,對於集合S,就是保證了原給定的區域性極小值必定成立,但不能保證其它位置不是區域性極小值的一種情況的答案。求出所有的這些之後,容斥一下就可以了。
如果有誤在評論區吼一聲哦!
程式碼:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int px[]={0,0,1,-1,1,-1,1,-1},py[]={1,-1,1,-1,-1,1,0,0},mod=12345678;
int n,m,ans,tot,mp[6][10],vis[6][10],dp[30][(1<<8)+10],x[10],y[10],g[(1<<8)+10];
char s[10];
int DP(){
tot=0;
for(int i=1 ;i<=n;i++)
for(int j=1;j<=m;j++)
if(mp[i][j]){
x[tot]=i;
y[tot++]=j;
}
for(int i=0;i<(1<<tot);i++){
g[i]=n*m;
memset(vis,0,sizeof(vis));
for(int j=0;j<tot;j++)
if(!(i&(1<<j))){
for(int k=0;k<8;k++){
int tx=x[j]+px[k],ty=y[j]+py[k];
if(tx<=n&&ty<=m&&tx>0&&ty>0&&!vis[tx][ty]){
vis[tx][ty]=1;
g[i]--;
}
}
g[i]--;
vis[x[j]][y[j]]=1;
}
}
dp[0][0]=1;
for(int i=1;i<=n*m;i++){
for(int j=0;j<(1<<tot);j++){
dp[i][j]=1ll*dp[i-1][j]*max(g[j]-i+1,0)%mod;
for(int k=0;k<tot;k++){
if(j&(1<<k)){
dp[i][j]+=dp[i-1][j^(1<<k)];
if(dp[i][j]>=mod)
dp[i][j]-=mod;
}
}
}
}
return dp[n*m][(1<<tot)-1];
}
void dfs(int x,int y,int k){
if(x==n+1){
ans+=k*DP();
if(ans>=mod)
ans-=mod;
if(ans<0)
ans+=mod;
return;
}
if(y==m+1){
dfs(x+1,1,k);
return;
}
dfs(x,y+1,k);
if(mp[x][y])
return;
for(int i=0;i<8;i++)
if(mp[x+px[i]][y+py[i]])
return;
mp[x][y]=1;
dfs(x,y+1,-k);
mp[x][y]=0;
return;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%s",s+1);
for(int j=1;j<=m;j++)
mp[i][j]=(s[j]=='X');
}
dfs(1,1,1);
printf("%d",ans);
return 0;
}