=== ===
这里放传送门
=== ===
题解
现在看这题的代码感觉这题写起来好GG啊。。是我当时太蠢了吗。。。没错是这样对吧。。。
首先局部最小值这个东西有一个很重要的性质就是局部最小值是不能相邻的,不然它们两个就会发生冲突然后就不知道到底谁是最小值了。所以这样的话在题目给定的这个格子大小范围内局部最小值的位置最多有8个。这样的话就可以把局部最小值状压起来啦。
考虑如果我们已经知道了哪些位置一定是局部最小值如何得到当前满足要求的方案数。肯定是用 1..nm 这些数字往格子里填。但是按照什么顺序填呢?如果一行行一列列那样填的话每个格子会被周围一圈格子影响所以这个递推顺序是不大对的。那么我们换一种方法,按照 1..nm 的顺序填,每次填到一个数字的时候,与它相关的局部最小值必须已经填上数字了。这样就能保证局部最小值一定比周围一圈的数字要小。
那么就可以用
f[k][S]
表示当前填到
k
这个数字,局部极小值的填写状态为
这样就可以了吗。。不不不这题才不会这么良心。。因为这么填写的时候只能保证题目给定的位置一定成为了局部极小值,但不能保证题目没有给定的位置不成为局部极小值,也就是可能有一些多出来的局部极小值。那就要用到容斥了。答案就是【初始答案】-【多出一个局部极小值的答案】+【多出两个局部极小值的答案】-【多出三个局部极小值的答案】+…
同样因为局部极小值的位置很少,所以可以用dfs爆搜当前的局部极小值状态。如果是题目已经给定的位置就直接记录,否则如果这个位置没有和任何已经存在的局部极小值相邻,它就是有可能成为极小值的位置。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#define Mod 12345678
using namespace std;
int n,m,cnt,p[500],f[30][500],pos[10][2],ans,vis[10][10],ext[10][10];
int d[8][2]={{0,1},{0,-1},{1,0},{-1,0},{-1,1},{1,-1},{1,1},{-1,-1}};
void AddIn(int dlt,int x,int y){
int tot=0;
vis[x][y]+=dlt;
for (int i=0;i<8;i++){
int u=x+d[i][0],v=y+d[i][1];
if (u<=n&&u>=1&&v<=m&&v>=1)
vis[u][v]+=dlt;
}
}
void work_p(int i,int now,int tot){
int delta=0,x,y;
if (i==cnt+1){
for (int i=1;i<=cnt;i++)
if ((now&(1<<(i-1)))!=0) ++delta;//把已经填过的极小值也算进去
p[now]=tot+delta;return;
}
work_p(i+1,now,tot);
x=pos[i][0];y=pos[i][1];
for (int j=0;j<8;j++){
int u=x+d[j][0],v=y+d[j][1];
if (u<=n&&u>=1&&v<=m&&v>=1){
ext[u][v]-=1;
if (ext[u][v]==0) ++delta;
}
}
work_p(i+1,now|(1<<(i-1)),tot+delta);
for (int j=0;j<8;j++){
int u=x+d[j][0],v=y+d[j][1];
if (u<=n&&u>=1&&v<=m&&v>=1)
ext[u][v]+=1;
}
}
int Calc(){
int tot=0;
memset(f,0,sizeof(f));
memset(p,0,sizeof(p));
memcpy(ext,vis,sizeof(ext));
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
if (ext[i][j]==0) ++tot;//计算所有极小值都没填的时候有多少位置能填数
work_p(1,0,tot);
f[0][0]=1;
for (int i=1;i<=n*m;i++)
for (int j=0;j<1<<(cnt);j++){
f[i][j]=f[i-1][j]*(p[j]-i+1)%Mod;
for (int k=1;k<=cnt;k++){
int w=(1<<(k-1));
if ((j&w)!=0) f[i][j]=(f[i][j]+f[i-1][j-w])%Mod;
}
}
return f[n*m][(1<<cnt)-1]%Mod;
}
void Dfs(int x,int y,int tot){
if (x>n||y>m) return;
int nx,ny,cal;
nx=x;ny=y+1;
if (y==m){nx=x+1;ny=1;}
Dfs(nx,ny,tot);
if (vis[x][y]==0){
pos[++cnt][0]=x;pos[cnt][1]=y;//增加一个多出来的极小值
AddIn(1,x,y);
cal=Calc();
if ((tot+1)%2==0)
ans=(ans+cal)%Mod;//容斥
else ans=(ans-cal)%Mod;
Dfs(nx,ny,tot+1);
--cnt;AddIn(-1,x,y);
}
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++){
char c=getchar();
while (c!='.'&&c!='X') c=getchar();
if (c=='X'){
pos[++cnt][0]=i;pos[cnt][1]=j;
AddIn(1,i,j);
}
}
ans=Calc();
Dfs(1,1,0);
printf("%d\n",(ans%Mod+Mod)%Mod);
return 0;
}
偏偏在最后出现的补充说明
n很小的时候基本上就是要么容斥要么状压
这个题恰好都用到了= =