P3160:局部极小值(容斥、状压)

解析

又是一道我不会的容斥题
qwq
本题的一个关键性质:答案有解时,极小值不超过8个
所以可以对其进行状压

考虑从小到大填数
那么在极小值填完之前,它的八连通必然是不能填的

设计 d p i , s dp_{i,s} dpi,s表示从小到大填了i个数,已经填完的极小值状态为s的方案数
不难作出转移

但是这样会统计一些不合法的方案!
有的非极小值可能由于周围全是非极小值,又随便填,导致成为了极小值
所以要扣去所有非极小值成为极小值的方案

方法就是dfs枚举哪些非极小值成为极小值dp再按这些非极小值的个数的奇偶性进行容斥

代码

//暴力
#include<bits/stdc++.h>
using namespace std;
const int mod=12345678;
#define ll long long
#define il inline
il ll read(){
  ll x=0,f=1;char c=getchar();
  while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
  while(isdigit(c)){x=x*10+c-'0';c=getchar();}
  return x*f;
}
int n,m;
bool jd[20][20];
int a[20][20];
int dx[9]={0,0,-1,-1,-1,0,1,1,1},dy[9]={0,-1,-1,0,1,1,1,0,-1};
int ans;
inline bool exi(int x,int y){
  return x>=1&&x<=n&&y>=1&&y<=m;
}
int x[50],y[50],tot,mi[50];
bool vis[12][12];
int dp[35][1050];
int num[1050];
int calc(){
  tot=0;
  for(int i=1;i<=n;i++){
    for(int j=1;j<=m;j++){
      if(jd[i][j]){
	++tot;x[tot]=i;y[tot]=j;
      }
    }
  }
  for(int s=0;s<mi[tot];s++){
    memset(vis,0,sizeof(vis));
    num[s]=n*m;
    for(int i=1;i<=tot;i++){
      if(s&mi[i-1]) continue;
      int xx=x[i],yy=y[i];
      if(!vis[xx][yy]){
	vis[xx][yy]=1;num[s]--;
      }
      for(int k=1;k<=8;k++){
	int nx=xx+dx[k],ny=yy+dy[k];
	if(exi(nx,ny)&&vis[nx][ny]==0){
	  vis[nx][ny]=1;num[s]--;
	}
      }
    }
    //printf("s=%d num=%d\n",s,num[s]);
  }
  memset(dp,0,sizeof(dp));
  dp[0][0]=1;
  for(int i=1;i<=n*m;i++){
    for(int s=0;s<mi[tot];s++){
      dp[i][s]+=1ll*dp[i-1][s]*max(num[s]-i+1,0)%mod;
      if(dp[i][s]>=mod) dp[i][s]-=mod;
      for(int k=1;k<=tot;k++){
	if((s&mi[k-1])==0) continue;
	dp[i][s]+=dp[i-1][s-mi[k-1]];
	if(dp[i][s]>=mod) dp[i][s]-=mod;
      }
    }
  }
  return dp[n*m][mi[tot]-1];
}
void dfs(int x,int y,int o){
  if(x>n){
    /*for(int i=1;i<=n;i++){
      for(int j=1;j<=m;j++) printf("%d ",jd[i][j]);
      putchar('\n');
    }
    */
    if(o&1){
      ans-=calc();
      if(ans<0) ans+=mod;
    }
    else{
      ans+=calc();
      if(ans>=mod) ans-=mod;
    }
    //printf("ans=%d\n\n",ans);
    return;
  }
  if(y>m){
    dfs(x+1,1,o);
    return;
  }
  dfs(x,y+1,o);
  if(!jd[x][y]){
    for(int i=1;i<=8;i++){
      int nx=x+dx[i],ny=y+dy[i];
      if(exi(nx,ny)&&jd[nx][ny]) return;
    }
    jd[x][y]=1;
    dfs(x,y+1,o+1);
    jd[x][y]=0;
  }
}
int main(){
#ifndef ONLINE_JUDGE
  freopen("a.in","r",stdin);
  freopen("a.out","w",stdout);
  #endif
  mi[0]=1;
  for(int i=1;i<=28;i++) mi[i]=mi[i-1]<<1;
  n=read();m=read();
  char c;
  for(int i=1;i<=n;i++){
    for(int j=1;j<=m;j++){
      scanf(" %c",&c);
      jd[i][j]=c=='X';
    }
  }
  for(int i=1;i<=n;i++){
    for(int j=1;j<=m;j++){
      if(!jd[i][j]) continue;
      for(int k=1;k<=8;k++){
	int nx=i+dx[k],ny=j+dy[k];
	if(exi(nx,ny)&&jd[nx][ny]){
	  printf("0");return 0;
	}
      }
    }
  }
  dfs(1,1,0);
  printf("%d\n",ans);
}
/*

 */

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值