Description
Solution
很明显这题是DP,不过比赛的时候没有考虑状压。
因为有X的点最多只有8个,那么可以考虑状压。
因为要求有X的点比周围的点小,那么可以把数从小到大填。
设f[i][j]表示数值已经填到了第i个数,X点填的状态为j。
转移方程:
f[i][j]=∑k∈jf[i−1][j−k]+max(0,rest[j]−i+1)∗f[i−1][j]
rest[j]表示这个j这个状态下可以随意填的点的个数(不计算其他在j状态下不包括X的点能控制的点,因为这些点填了会有后效性)。
因为题目要求只有X的点的值比周围的值都小,那么可能会有一些3*3的都是’.’的点那个中间的点也可能值比周围的都小。
所以,现在需要容斥一下。
Code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const int mo=12345678;
int i,j,k,l,t,n,m,cas;
ll ans;
char s[10][10];
int f[30][1<<8],rest[1<<8];
int fang[8][2]={1,0,0,1,-1,0,0,-1,1,1,-1,-1,1,-1,-1,1};
bool az[10][10];
int suan(){
int i,j,k,l=0,u=0,ii,jj=0;
fo(i,1,n)fo(j,1,m)if(s[i][j]=='X')l++;
memset(rest,0,sizeof(rest));
fo(ii,0,(1<<l)-1){
memset(az,0,sizeof(az));
jj=0;
fo(i,1,n){
fo(j,1,m){
if(s[i][j]=='X'){
jj++;
if((ii&(1<<(jj-1))))continue;
az[i][j]=1;
fo(k,0,7){
int x=fang[k][0]+i,y=fang[k][1]+j;
if(x<1||x>n||y<1||y>m)continue;
az[x][y]=1;
}
}
}
}
fo(i,1,n)fo(j,1,m)if(!az[i][j])rest[ii]++;
}
f[0][0]=1;
fo(i,1,n*m){
fo(j,0,(1<<l)-1){
f[i][j]=(ll)f[i-1][j]*max(rest[j]-i+1,0)%mo;
fo(k,1,l){
if(j&(1<<(k-1)))f[i][j]=(ll)(f[i][j]+f[i-1][j-(1<<(k-1))])%mo;
}
}
}
return f[n*m][(1<<l)-1];
}
void dfs(int x,int y,int z){
if(y==m+1){
dfs(x+1,1,z);
return;
}
if(x==n+1){
int u=(z>0)?-1:1;
ans=(ans+u*suan()+mo)%mo;
return;
}
bool bz=1;
if(s[x][y]=='X')bz=0;
dfs(x,y+1,z);
fo(i,0,7){
int xx=fang[i][0]+x,yy=fang[i][1]+y;
if(xx<1||xx>n||yy<1||yy>m||s[xx][yy]=='.')continue;
bz=0;
}
if(bz){
s[x][y]='X';
dfs(x,y+1,z^1);
s[x][y]='.';
}
}
int main(){
for(scanf("%d",&cas);cas;cas--){
scanf("%d%d",&n,&m);
fo(i,1,n){
scanf("%s",s[i]+1);
}
ans=0;
fo(i,1,n){
fo(j,1,m){
if(s[i][j]=='X')
fo(k,0,7){
int x=fang[k][0]+i,y=fang[k][1]+j;
if(x<1||x>n||y<1||y>m||s[x][y]=='.')continue;
printf("0\n");
ans=-1;
break;
}
if(ans==-1)break;
}
if(ans==-1)break;
}
if(ans==-1){
continue;
}
ans=0;
dfs(1,1,0);
printf("%lld\n",ans);
}
}