状态压缩

首先是状态压缩涉及到的一些小知识点:

  • 判断一个数字x二进制下第i位是不是等于1。
    方法:if(((1<<(i−1))&x)>0)

    将1左移i-1位,相当于制造了一个只有第i位上是1,其他位上都是0的二进制数。然后与x做与运算,如果结果>0,说明x第i位上是1,反之则是0。
  • 将一个数字x二进制下第i位更改成1。
    方法:x=x|(1<<(i−1))
    证明方法与1类似,此处不再重复证明。
  • 把一个数字二进制下最靠右的第一个1去掉。
    方法:x=x&(x−1)

状压其实是一种很暴力的算法,因为他需要遍历每个状态,所以将会出现2n的情况数量,不过这并不代表这种方法不适用:一些题目可以依照题意,排除不合法的方案,使一行的总方案数大大减少从而减少枚举

【例1】有一个N*M(N<=5,M<=1000)的棋盘,现在有1*2及2*1的小木块无数个,要盖满整个棋盘,有多少种方式?答案只需要mod1,000,000,007即可。

例如:对于一个2*2的棋盘,有两种方法,一种是使用2个1*2的,一种是使用2个2*1的。
在这道题目中,N和M的范围本应该是一样的,但实际上,N和M的范围却差别甚远,对于这种题目,首先应该想到的就是,正确算法与这两个范围有关!N的范围特别小,因此可以考虑使用状态压缩动态规划的思想.

假设第一列已经填满,则第二列的摆设方式,只与第一列对第二列的影响有关。同理,第三列的摆设方式也只与第二列对它的影响有关。那么,使用一个长度为N的二进制数state来表示这个影响。
因此,本题的状态可以这样表示:

dp[i][state]表示该填充第i列,第i-1列对它的影响是state的时候的方法数。i<=M,0<=state<2N

对于每一列,情况数也有很多,但由于N很小,所以可以采取搜索的办法去处理。对于每一列,搜索所有可能的放木块的情况,并记录它对下一列的影响,之后更新状态。状态转移方程如下:

dp[i][state]=∑dp[i-1][pre]每一个pre可以通过填放成为state

对于每一列的深度优先搜索,写法如下:

//第i列,枚举到了第j行,当前状态是state,对下一列的影响是nex
void dfs(int i,int j,int state,int nex)
{
	if (j==N)
	{
		dp[i+1][nex]+=dp[i][state];
		dp[i+1][nex]%=mod;
		return;
	}
	//如果这个位置已经被上一列所占用,直接跳过
	if (((1<<j)&state)>0)
		dfs(i,j+1,state,nex);
	//如果这个位置是空的,尝试放一个1*2的
	if (((1<<j)&state)==0)
		dfs(i,j+1,state,nex|(1<<j));
	//如果这个位置以及下一个位置都是空的,尝试放一个2*1的
	if (j+1<N && ((1<<j)&state)==0 && ((1<<(j+1))&state)==0)
		dfs(i,j+2,state,nex);
	return;
}

状态转移的方式如下:

for (int i=1;i<=M;i++)
	{
		for (int j=0;j<(1<<N);j++)
		if (dp[i][j])
		{
			dfs(i,0,j,0);
		}
	}

最终,答案就是dp[M+1][0]。

  • 12
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值