●BZOJ 2669 [cqoi2012]局部极小值

Posted *ZJ

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了●BZOJ 2669 [cqoi2012]局部极小值相关的知识,希望对你有一定的参考价值。

题链:

http://www.lydsy.com/JudgeOnline/problem.php?id=2669

题解:

容斥,DP,DFS


先看看 dp 部分:
首先呢,X的个数不会超过 8个
个数很少,所以考虑状压,把需要填 X的那几个位置状压为二进制10表示对应的那个X位置是否已经填数。
同时填的数互不重复,考虑从小填到大。
cnt[S] 表示除了不在集合 S 里的 X 位置及其周围的位置,剩下的位置个数
定义 dp[i][S]表示从小到大填数填完了i这个数,且已经填了的 S 这个集合里的 X 位置的方案数
转移:依次去填数 1~N*M,每次有两种选择:
1).把这个数填在 某个 X 位置(枚举一个 k表示第 k个 X 位置填当前数)
dp[i][S]+=dp[i-1][S^(1<<(k-1))]

2).把这个数填在非 X 位置,那么填的位置有 cnt[S]-(i-1) 种。
dp[i][s]+=dp[i-1][s]*(cnt[s]-(i-1)) (好好理解一下这个转移)

这样 dp 可以保证那些给出的 X 位置一定是局部最小值,
因为第二种转移的填数位置都不能填在还没有填数的 X 位置的周围。
所以就完了么?
当然还没有,尽管我们保证了给出的 X 位置一定是局部最小值,
但是没有保证非 X位置一定不是非局部最小值。即,求出来的 dp[N*M][all_S(全集)]的意思是至少all_S集合里的 X位置为局部最小值的方案数
所以容斥如下:
ANS = 至少多填了0个局部最小值的方案数(dp[N*M][all_S])
          -至少多填了1个局部最小值的方案数
         +至少多填了2个局部最小值的方案数
          -....+ ....
这些用于容斥的方案数的求法:
DFS 搜索出哪些非 X 位置还可以改为 X ,
然后对于每一种新的填法,去跑一遍上述的dp即可求得对应的方案数。

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#define _ % mod
#define filein(x) freopen(#x".in","r",stdin);
#define fileout(x) freopen(#x".out","w",stdout);
using namespace std;
const int mv[9][2]={{0,0},{-1,0},{-1,1},{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1}};
const int mod=12345678;
char mp[10][10];
int N,M,ANS;
int solve(){
	static bool vis[10][10];
	static int dp[30][1<<8],cnt[1<<8],x[10],y[10],tot,tmp;
	tot=0; memset(dp,0,sizeof(dp));
	for(int i=1;i<=N;i++)
		for(int j=1;j<=M;j++) if(mp[i][j]==‘X‘) 
			tot++,x[tot]=i,y[tot]=j;
	for(int s=0;s<1<<tot;s++){
		tmp=0; memset(vis,0,sizeof(vis));
		for(int i=1;i<=tot;i++) if(!(s&(1<<(i-1))))
			for(int k=0;k<9;k++)
				vis[x[i]+mv[k][0]][y[i]+mv[k][1]]=1;
		for(int i=1;i<=N;i++)
			for(int j=1;j<=M;j++) 
				if(!vis[i][j]) tmp++;
		cnt[s]=tmp;
	}
	dp[0][0]=1;
	for(int i=1;i<=N*M;i++)
		for(int s=0;s<1<<tot;s++){
			dp[i][s]=(1ll*dp[i][s]+1ll*dp[i-1][s]*max(cnt[s]-(i-1),0)_)_;
			for(int k=1;k<=tot;k++) if(s&(1<<(k-1)))
				dp[i][s]=(1ll*dp[i][s]+dp[i-1][s^(1<<(k-1))])_;
		}
	return dp[N*M][(1<<tot)-1];
}
void dfs(int x,int y,int t){
	if(y==M+1){dfs(x+1,1,t);return;}
	if(x==N+1){
		int tmp=solve();
		if(t&1) tmp=(-1ll*tmp+mod)_;
		ANS=((1ll*ANS+tmp)_+mod)_;
		return;
	}
	dfs(x,y+1,t);
	bool fg=1;
	for(int k=0;k<9;k++) 
		if(mp[x+mv[k][0]][y+mv[k][1]]==‘X‘) fg=0;
	if(fg){
		mp[x][y]=‘X‘;
		dfs(x,y+1,t+1);
		mp[x][y]=‘.‘;
	}
}
int main()
{
	scanf("%d%d",&N,&M);
	for(int i=1;i<=N;i++) 
		scanf("%s",mp[i]+1);
	dfs(1,1,0);
	printf("%d",ANS);
	return 0;
}

以上是关于●BZOJ 2669 [cqoi2012]局部极小值的主要内容,如果未能解决你的问题,请参考以下文章

BZOJ 2669 CQOI2012 局部极小值 状压dp+容斥原理

bzoj2669 [cqoi2012]局部极小值 状压DP+容斥

2669[cqoi2012]局部极小值 容斥+状压dp

NOIP2016提高A组模拟8.15Garden

P3160 [CQOI2012]局部极小值

[cqoi2012]局部极小值