状态压缩

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

  • 判断一个数字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]。

你可能感兴趣的:(ACM专题)