[BZOJ2669]局部极小值

本文介绍了一种使用状压DP方法求解在特定条件下放置数字使得某些位置成为局部极小值的问题。通过定义状态转移方程,利用容斥原理计算最终答案。

终于补回来了...

考虑状压DP,设$f_{i,j}$表示已经填了$1\cdots i$,占用局部极小值状态为$j$的方案数,$g_i$表示除去状态为$\text{~}i$的局部极小值格子及其八邻居后还剩多少格子,这些格子满足$i$的局部极小值都被占据后再放新数仍然是合法的

转移时,我们可以把新数放在局部极小值的位置上,也可以放在其他地方,那么转移就是$f_{i,j}=f_{i-1,j}(g_j-(i-1))+\sum\limits_{k\in j}f_{i-1,j-k}$

设题目中给出局部极小值的集合为$S$,那么答案不是$f_{nm,S}$,因为它的含义是“至少满足$S$中格子都是局部极小值”,它还包括了一些$x\notin S$但$x$是局部极小值的情况

所以考虑容斥,答案是$\sum\limits_{S\subset S'}(-1)^{|S'|-|S|}f_{nm,S'}$,搜索$S'$并DP即可,搜索时把$S'-S$中有格子与$S$中格子八邻居的情况剪掉会快很多

#include<stdio.h>
#include<string.h>
typedef long long ll;
const int mod=12345678,go[8][2]={{-1,-1},{-1,0},{-1,1},{0,-1},{0,1},{1,-1},{1,0},{1,1}};
int mul(int a,int b){return a*(ll)b%mod;}
void inc(int&a,int b){(a+=b)%=mod;}
char s[6][9];
int n,m,cm,pos[26][2];
bool ch[26],mp[6][9];
bool ok(int x,int y){return 0<x&&x<=n&&0<y&&y<=m;}
bool check(){
	int i,j,k;
	memset(mp,0,sizeof(mp));
	for(i=1;i<=cm;i++){
		if(ch[i])mp[pos[i][0]][pos[i][1]]=1;
	}
	for(i=1;i<=n;i++){
		for(j=1;j<=m;j++){
			if(mp[i][j]){
				for(k=0;k<8;k++){
					if(mp[i+go[k][0]][j+go[k][1]])return 0;
				}
			}
		}
	}
	return 1;
}
int f[29][256],g[256],bit[256],xp[10][2],cx;
int dp(){
	int N,i,j,k,x,y,tx,ty;
	N=1<<cx;
	memset(g,0,N<<2);
	for(i=0;i<N;i++){
		memset(mp,0,sizeof(mp));
		for(j=0;j<cx;j++){
			x=xp[j+1][0];
			y=xp[j+1][1];
			if(~i>>j&1){
				mp[x][y]=1;
				for(k=0;k<8;k++){
					tx=x+go[k][0];
					ty=y+go[k][1];
					if(ok(tx,ty))mp[tx][ty]=1;
				}
			}
		}
		for(j=1;j<=n;j++){
			for(k=1;k<=m;k++)g[i]+=!mp[j][k];
		}
	}
	f[0][0]=1;
	for(i=1;i<=n*m;i++){
		for(j=0;j<N;j++){
			if(i>=bit[j]){
				f[i][j]=mul(f[i-1][j],g[j]-(i-1));
				for(k=0;k<cx;k++){
					if(j>>k&1)inc(f[i][j],f[i-1][j^(1<<k)]);
				}
			}
		}
	}
	return f[n*m][N-1];
}
int ans;
void dfs(int now,int siz){
	if(now>cm)return;
	ch[now]=0;
	dfs(now+1,siz);
	ch[now]=1;
	if(check()){
		siz++;
		cx++;
		xp[cx][0]=pos[now][0];
		xp[cx][1]=pos[now][1];
		inc(ans,(siz&1?-1:1)*dp());
		dfs(now+1,siz);
		cx--;
	}
	ch[now]=0;
}
int main(){
	int i,j,k,x,y;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++){
		scanf("%s",s[i]+1);
		for(j=1;j<=m;j++){
			if(s[i][j]=='X'){
				cx++;
				xp[cx][0]=i;
				xp[cx][1]=j;
			}
		}		
	}
	if(cx==0){
		putchar('0');
		return 0;
	}
	for(i=1;i<=n;i++){
		for(j=1;j<=m;j++){
			if(s[i][j]=='.'){
				for(k=0;k<8;k++){
					x=i+go[k][0];
					y=j+go[k][1];
					if(ok(x,y)&&s[x][y]=='X'){
						k=-1;
						break;
					}
				}
				if(~k){
					cm++;
					pos[cm][0]=i;
					pos[cm][1]=j;
				}
			}
		}
	}
	for(i=1;i<256;i++)bit[i]=bit[i>>1]+(i&1);
	ans=dp();
	dfs(1,0);
	inc(ans,mod);
	printf("%d",ans);
}

转载于:https://www.cnblogs.com/jefflyy/p/9391659.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值