状态压缩dp
例:
有一个n*m的棋盘(n、m≤80,n*m≤80)要在棋盘上放k(k≤20)个棋子,使得任意两个棋子不相邻(每个棋子最多和周围4个棋子相邻)。求合法的方案总数。
直接考虑解决这个问题并不容易,我们先来考虑这个问题的退化形式:现在我们令n=1。则我们可以很容易的想到状态转移方程:
设dp[i][j][0]表示当前到达第i列,一共使用了j个旗子,且当前格子的状态为不放的状态总数,类似的 dp[i][j][1]就是当前格子的状态为放的状态总数。那么状态转移方程就是
dp[i][j][0]=dp[i-1][j][1]+dp[i-1][j][0];
dp[i][j][1]=dp[i-1][j-1][0];当n=1的时候这个问题无疑是非常简单的,但是如果我们想模仿这种做法来解决原问题的话,就会遇到这样的问题:如何来表示当前行的状态?
昨天已经提到了一些状态压缩的知识,如果看懂了的话,应该已经明白怎么做了。
对于每一行,如果把没有棋子的地方记为0,有棋子的地方记为1,那么每一行的状态都可以表示成一个2进制数,进而将其转化成10进制。那么这个问题的状态转移方程就变成了设dp[ i ] [ j ][k ]表示当前到达第i列,一共使用了j个棋子,且当前行的状态在压缩之后的十进制数为k 时的状态总数。
那么我们也可以类似的写出状态转移方程:
dp[ i ][ j ][ k ]=sum( dp[ i-1][ j-num(k) ][ w ] ) num(k)表示 k状态中棋子的个数,w表示前一行的状态。虽然写出了状态转移方程,但是还是有很多细节问题需要解决:比如,如何保证当前状态是合法的?
最基本的做法是:首先判断k状态是否合法,也就是判断在这一行中是否有2个旗子相邻,然后枚举上一行的状态w,判断w状态是否合法,然后判断k状态和w状态上下之间是否有相邻的棋子。进一步优化,比如:判断每一行状态是否合法,可以在程序一开始判断然后保存结果,判断k状态和w状态上下之间是否有相邻的棋子,可以利用位运算,if(k&w)说明上下之间有相邻的棋子等等。