bzoj 2669 题解(状压dp+搜索+容斥原理)

这题太难了...看了30篇题解才整明白到底咋回事...

核心思想:状压dp+搜索+容斥

首先我们分析一下,对于一个4*7的棋盘,低点的个数至多只有8个(可以数一数)

这样的话,我们可以进行一个状压,把所有的低点压进来

然后我们从小到大枚举所有数,转移即可

记状态f[i][j]表示到了第i个数,低点的状态为j的方案数

那么在转移的时候,有两个转移方向:

①.如果第i个数放在低点上,那么我们可以枚举所有的低点k,如果低点没有在状态里,有:

dp[i][j|(1<<k)]+=dp[i-1][j]

②.如果第i个数放在高点上,那么我们需要枚举所有可以使用的高点,所谓可以使用的高点就是指的某一高点周围没有未使用的低点(原因:我是从小往大枚举的所有数,如果我在一个高点上放了一个数而他附近却有低点没有放上,那么这个低点会变得比这个高点高,这就是不合法的了。)

但是如果每次都枚举,时间复杂度是会爆炸的,所以我们需要进行一个预处理num[i],表示i状态下有多少个高点可以使用

最后的答案就是dp[n*m][1<<cnt-1]

可是这个答案是正确的吗?

我们发现,如果仅仅是这么枚举,很容易出现一种情况:某个点本来是高点,但是由于随意的放置使得这个点变成了低点

所以我们需要解决掉这个问题

怎么解决?

很显然,我们只需求出把每个可能被放错的高点真正作为低点的方案数,然后用总方案数减掉这个方案数就可以了。

可是,如果我们分别去减,由于减的方案数还要像上面那样dp出来,所以会产生更多的重复(即高点1和高点2同时放错)

所以我们需要利用容斥,即

0个高点反转-1个高点反转+2个高点反转....

(反转指反转状态)

那么怎么反转?

dfs!

贴代码:

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#define ll long long
#define mode 772002
using namespace std;
bool used[10][10];
bool maps[10][10];
int n,m;
ll dp[30][(1<<15)+5];
char ch[10];
int dir[10][2]={{0,0},{1,1},{1,0},{1,-1},{0,1},{0,-1},{-1,1},{-1,0},{-1,-1}};
int temp[30][2];
int num[(1<<15)+5];
ll ans=0;
bool check(int x,int y)
{
	if(x>0&&x<=n&&y>0&&y<=m)
	{
		return 1;
	}
	return 0;
}
ll get_dp()
{
	int cnt=0;
	memset(dp,0,sizeof(dp));
	memset(used,0,sizeof(used));
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if(maps[i][j])
			{
				used[i][j]=1;
				temp[++cnt][0]=i;
				temp[cnt][1]=j;
			}
		}
	}
	for(int i=0;i<=(1<<cnt)-1;i++)
	{
		int tot=0;
		memset(used,0,sizeof(used));
		for(int j=1;j<=cnt;j++)
		{
			if(!((1<<(j-1))&i))
			{
				if(!used[temp[j][0]][temp[j][1]])
				{
					used[temp[j][0]][temp[j][1]]=1;
					tot++;
				}
				for(int t=1;t<=8;t++)
				{
					int x=temp[j][0]+dir[t][0];
					int y=temp[j][1]+dir[t][1];
					if(check(x,y)&&!used[x][y])
					{
						used[x][y]=1;
						tot++;
					}
				}
			}
		}
		num[i]=n*m-tot;
	}
	dp[0][0]=1;
	for(int i=1;i<=n*m;i++)
	{
		for(int j=0;j<=(1<<cnt)-1;j++)
		{
			dp[i][j]+=dp[i-1][j]*max(num[j]-i+1,0)%mode;
			dp[i][j]%=mode;
			for(int k=1;k<=cnt;k++)
			{
				if(!((1<<(k-1))&j))
				{
					dp[i][j|(1<<(k-1))]+=dp[i-1][j];
					dp[i][j|(1<<(k-1))]%=mode;
				}
			}
		}
	}
	return dp[n*m][(1<<cnt)-1];
}
void dfs(int x,int y,int typ)
{
	if(x==n+1)
	{
		if(typ%2)
		{
			ans-=get_dp();
			ans%=mode;
		}else
		{
			ans+=get_dp();
			ans%=mode;
		}
		return;
	}
	if(y==m+1)
	{
		dfs(x+1,1,typ);
		return;
	}
	dfs(x,y+1,typ);
	if(!maps[x][y])
	{
		bool flag=0;
		for(int i=1;i<=8;i++)
		{
			int st=x+dir[i][0];
			int ed=y+dir[i][1];
			if(maps[st][ed])
			{
				flag=1;
				break;
			}
		}
		if(!flag)
		{
			maps[x][y]=1;
			dfs(x,y+1,typ+1);
			maps[x][y]=0;
			return;
		}
	}
}
int main()
{
	int cot=0;
	while(scanf("%d%d",&n,&m)!=EOF)
	{
		cot++;
		bool u=0;
		memset(maps,0,sizeof(maps));
		for(int i=1;i<=n;i++)
		{
			scanf("%s",ch);
			for(int j=0;j<m;j++)
			{
				if(ch[j]=='.')
				{
					maps[i][j+1]=0;
				}else
				{
					maps[i][j+1]=1;
					if(maps[i-1][j+1]||maps[i][j])
					{
						u=1;
					}
				}
			}
		}
		if(u)
		{
			printf("Case #%d: 0\n",cot);
			continue;
		}
		ans=0;
		dfs(1,1,0);
		printf("Case #%d: %lld\n",cot,(ans%mode+mode)%mode);
	}
	return 0;
} 

 

posted @ 2018-08-17 15:58  lleozhang  Views(188)  Comments(0Edit  收藏  举报
levels of contents