题解 - [CQOI2012]局部极小值

题解 - [ C Q O I 2012 ] \mathrm{[CQOI2012]} 局部极小值

  • 一道超神状压 D P DP ,总算看懂题解。。。这里来写写自己的心路历程。

题目意思

S o l \mathrm{Sol}

  • 首先要明白为什么能用状压做这道题目,因为最多只会存在 8 8 个局部最小值(即各一个 . '.' 放一个 X 'X' )
  • 于是就有 f i , S f_{i,S} 表示数字填到 i i ,此时填 X 'X' 状态为 S S 的方案总数(这还是比较好理解的)
  • 然后我们来看如何转移
    • 当前数 i i 填入 X 'X' 中,那么我们就要从已经填过的 X 'X' 转移过来。即 f i , S = k ( ( 1 < < k 1 ) & S = = 0 ) f i 1 , S   x o r ( 1 < < k 1 ) f_{i,S}=\sum_{k|((1<<k-1)\&S==0)}^{} {f_{i-1,S\ xor (1<<k-1)}} 其中 S   x o r ( 1 < < k 1 ) S\ xor (1<<k-1) 表示这个状态 k k 这个坑没填过,现在要填就累计起来。
    • 当不填入 X 'X' 中,我们要先计算出有几种填法。对于那些没有填过的 X ‘X' 周围也不能填。于是就是总数 n m n*m 减去那些不合法的情况。于是转移就是上一个状态转移过来: f i , S = f i 1 , S × ( s u m i + 1 ) f_{i,S}=f_{i-1,S}\times (sum-i+1) s u m i + 1 sum-i+1 的意思就是一共合法的格子数为 s u m sum 减去已经用了的数字 i 1 i-1 那么就是 s u m ( i 1 ) sum-(i-1) 啦。还是比较好理解的。
  • 最后一步就是:我们在填数的时候很可能造成一种情况即:这个点本身不是 X 'X' 而他周围的数都比他大,那么我们肯定要容斥掉这样的情况,这个实现很简单,就是把这些点先当作 X 'X' ,最后再减掉即可。这个应该还是比较显然的。
  • 还有记得对于那种相邻 X 'X' 的情况直接输出 0 0 就可以了。

C o d e \mathrm{Code}

#include <bits/stdc++.h>
using namespace std;

inline int read()
{
	int sum=0,ff=1; char ch=getchar();
	while(!isdigit(ch))
	{
		if(ch=='-') ff=-1;
		ch=getchar();
	}
	while(isdigit(ch))
		sum=sum*10+(ch^48),ch=getchar();
	return sum*ff;
}

const int N=1<<9|1;
const int mo=12345678;
const int dx[10]={-1,-1,-1,0,0,1,1,1,0};
const int dy[10]={-1,0,1,-1,1,-1,0,1,0};

int n,m,ans,hi[N],f[35][N],vis[15][15];
int px[50],py[50],is[15][15],gs;
char ch[15];

inline int dp()
{
	memset(f,0,sizeof(f));
	f[0][0]=1;
	for ( int i=0;i<(1<<gs);i++ ) 
	{
		hi[i]=m*n;
		memset(vis,0,sizeof(vis)); 
		for ( int j=1;j<=gs;j++ ) 
			if(!(i&(1<<j-1))) 
				for ( int k=0;k<9;k++ ) 
					vis[px[j]+dx[k]][py[j]+dy[k]]=1;
		for ( int j=1;j<=n;j++ ) 
			for ( int k=1;k<=m;k++ ) 
				hi[i]-=vis[j][k];
	}
	//预处理可以放的方案数
	for ( int i=1;i<=m*n;i++ ) 
		for ( int j=0;j<(1<<gs);j++ ) 
		{
			if(hi[j]-i+1>0) 
				f[i][j]=(f[i][j]+f[i-1][j]*(hi[j]-i+1))%mo;//情况1
			for ( int k=1;k<=gs;k++ ) 
				if(j&(1<<k-1)) 
					f[i][j]=(f[i][j]+f[i-1][j^(1<<k-1)])%mo;//情况2
		}
	return f[n*m][(1<<gs)-1];
}

inline int dfs(int x,int y)
{
	if(y==m+1) x++,y=1;
	if(x==n+1) return dp();
	int now=dfs(x,y+1);
	for ( int i=0;i<9;i++ ) 
	{
		int tx=x+dx[i],ty=y+dy[i];
		if(is[tx][ty]) return now;
	}
	is[x][y]=1;
	px[++gs]=x,py[gs]=y;
	//先标记那些类'X'点
	now-=dfs(x,y+1);
	now=(now+mo)%mo;
	gs--,is[x][y]=0;
	//回溯,容斥掉那些类'X'点
	return now;
}

int main()
{
	n=read();
	m=read();
	for ( int i=1;i<=n;i++ ) 
	{
		scanf("%s",ch+1);
		for ( int j=1;j<=m;j++ ) 
			if(ch[j]=='X')
			{
				px[++gs]=i;
				py[gs]=j;
				is[i][j]=1;
				//处理出'X'的位置
			}
	}
	for ( int i=1;i<=gs;i++ ) 
		for ( int j=i+1;j<=gs;j++ ) 
			if(abs(px[i]-px[j])<=1&&abs(py[i]-py[j])<=1) 
				return printf("0\n"),0;//特判相邻'X'
	ans=dfs(1,1);
	printf("%d\n",ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/wangyiyang2/article/details/104962222