【BZOJ2669】[CQOI2012] 局部极小值(DFS套DP)
- 给定一个\(n\times m\)的网格图,其中的数恰好是\(1\sim n\times m\)。
- 定义一个位置是局部极小值当且仅当它比周围八个数都小,给出所有局部极小值的位置,求可能的网格图个数。
- \(n\le4,m\le7\)
状压\(DP\)
首先考虑,因为任意两个局部极小值不可能相邻,因此最多也只有\(\lceil\frac n2\rceil\times\lceil\frac m2\rceil=8\)个局部极小值。
对于这种问题容易想到去从小到大填入每一个数,那么一个局部极小值就必须在周围点之前被填写。
因此我们设立状态\(f_{w,s}\)表示已经填入了\(1\sim w\),已被填写的局部极小值的集合为\(s\)的方案数。
为了快速转移,我们预处理出\(g_s\)表示在已填写局部极小值集合为\(s\)时,不与任何未被填写的局部极小值相邻的非局部极小值位置个数。
显然这些位置必然包含已被填写的非局部极小值位置,因为现在相邻的位置之前就更加相邻,肯定无法填写。
那么\(w\)填在非局部极小值位置的转移就很明显了,而填在局部极小值的位置只要枚举填在哪个局部极小值直接相加即可。
但这样转移肯定存在问题,就是我们无法确保一个非局部极小值的位置一定不是局部极小值。
容斥:\(DFS\)暴搜状态
考虑容斥,我们直接\(DFS\)暴搜至少有哪些局部最小值来\(DP\)。
之前说过了,最多只会有\(8\)个局部极小值,因此状态数想想都不多。
实际效率极高,每个测试点只要\(2ms\)。
代码:\(O(\)玄学\()\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 4
#define M 7
#define K 8
#define X 12345678
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
const int dx[8]={1,-1,0,0,1,-1,1,-1},dy[8]={0,0,1,-1,1,-1,-1,1};//8个方向
int n,m;long long ans;char a[N+5][N+5];
int t,vis[N+5][M+5],sx[K+5],sy[K+5],c[1<<K],g[1<<K],f[N*M+5][1<<K];I int DP()//状压DP
{
RI i,j;for(t=0,i=1;i<=n;++i) for(j=1;j<=m;++j) a[i][j]=='X'&&(sx[++t]=i,sy[t]=j),vis[i][j]=-1;//存下所有局部极小值,初始化时间戳为-1
RI l=1<<t,s,x,y;for(s=0;s^l;++s) for(c[s]=c[s>>1]+(s&1),g[s]=n*m-t,i=1;i<=t;++i)//预处理g[s]
if(!(s>>i-1&1)) for(j=0;j^8;++j) a[x=sx[i]+dx[j]][y=sy[i]+dy[j]]=='.'&&vis[x][y]^s&&(vis[x][y]=s,--g[s]);
f[0][0]=1;for(RI w=1;w<=n*m;++w) for(s=0;s^l;++s)//状压DP求解f[s]
for(f[w][s]=1LL*f[w-1][s]*(g[s]-(w-1-c[s]))%X,i=1;i<=t;++i) s>>i-1&1&&Inc(f[w][s],f[w-1][s^(1<<i-1)]);
return f[n*m][l-1];
}
I void dfs(CI x,CI y,CI op)//dfs容斥
{
if(x>n) return (void)(ans+=op*DP());if(y>m) return dfs(x+1,1,op);if(dfs(x,y+1,op),a[x][y]=='X') return;//边界;不变
for(RI i=0;i^8;++i) if(a[x+dx[i]][y+dy[i]]=='X') return;a[x][y]='X',dfs(x,y+1,-op),a[x][y]='.';//选作局部极小值
}
int main()
{
RI i,j,k;for(scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%s",a[i]+1);
for(i=1;i<=n;++i) for(j=1;j<=m;++j) if(a[i][j]=='X') for(k=0;k^8;++k) if(a[i+dx[k]][j+dy[k]]=='X') return puts("0"),0;//初始状态就不合法
return dfs(1,1,1),printf("%d\n",(ans%X+X)%X),0;
}
待到再迷茫时回头望,所有脚印会发出光芒