题目
有一个n行m列的整数矩阵,其中1到nm之间的每个整数恰好出现一次。如果一个格子比所有相邻格子(相邻是指有公共边或公共顶点)都小,我们说这个格子是局部极小值。
给出所有局部极小值的位置,你的任务是判断有多少个可能的矩阵。
思路
看到数据范围马上想到状压dp。
先分析状态数,会发现局部极小值在一张图中最多只有8个,所以状态为已经实现了哪些局部极小值。\(dp[i][S]\)为已经填完数\([1,i]\),满足局部极小值为\(S\)的方案数。
则考虑从已知的位置向后转移,下一个填数的位置可以是某个未填的局部极小值,也可以是除了未填过的局部最小值旁边的点之外的其它点,我们可以预处理这些点的数目\(num[i]\)。
两个转移方程如下:
\[ dp[i+1][S+(1<<j)]\leftarrow{dp[i][S]}\\ dp[i+1][s]\leftarrow{(num[s]−(i−∣s∣))×dp[i][s]} \]
然后要注意的是状态的定义:我们当前的\(dp\)值仅仅满足了需要成为局部极小值的点成为了局部极小值,而不能保证是否除了这些点之外还有其它的局部极小值,所以这里做一个容斥就好了。
代码
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int mod=12345678;
void Add(int &x,int y){
x+=y;
if(x<0)x+=mod;
if(x>=mod)x-=mod;
}
int n,m;
char S[15][15];
struct node{int x,y;}P[10];
bool check(int x,int y){return x>=1&&x<=n&&y>=1&&y<=m;}
int dp[30][1<<8|5],tt,cnt[1<<8|5];
bool vis[15][15],mark[5][8];
int calc(){
for(int i=1;i<1<<tt;i++){
memset(mark,0,sizeof(mark));cnt[i]=0;
for(int j=0;j<tt;j++)
if(i&1<<j){
for(int x=P[j].x-1;x<=P[j].x+1;x++)
for(int y=P[j].y-1;y<=P[j].y+1;y++){
if(!check(x,y)||mark[x][y])continue;
mark[x][y]=1;cnt[i]++;
}
}
}
dp[0][0]=1;
for(int i=1;i<=n*m;i++){
for(int j=0;j<1<<tt;j++){
dp[i][j]=0;
Add(dp[i][j],1LL*dp[i-1][j]*(n*m-cnt[((1<<tt)-1)^j]-i+1)%mod);
for(int q=0;q<tt;q++)
if(j&1<<q)
Add(dp[i][j],dp[i-1][j^(1<<q)]);
}
}
return dp[n*m][(1<<tt)-1];
}
int dfs(int x,int y,int f){
if(x>n)return calc()*f;
if(y>m)return dfs(x+1,1,f);
int res=0;
if(S[x][y]=='X'){
P[tt++]=(node){x,y};
Add(res,dfs(x,y+1,f));tt--;
return res;
}
bool flag=1;
for(int i=x-1;i<=x+1;i++)
for(int j=y-1;j<=y+1;j++)
if(S[i][j]=='X'||vis[i][j])flag=0;
if(flag){
P[tt++]=(node){x,y};vis[x][y]=1;
Add(res,dfs(x,y+1,-f));tt--;vis[x][y]=0;
}
Add(res,dfs(x,y+1,f));
return res;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%s",S[i]+1);
printf("%d\n",dfs(1,1,1));
return 0;
}