优化DP专题
这是c++提高的第一讲
大纲
1.状压DP概念
2.例题
3.倍增 & RMQ
4.倍增优化DP
5.环形DP
6.环形DP的单调队列优化
1.状压DP概念
动态规划是解决“多阶段决策最优化问题”的一种算法思想。阶段的划分决定了状态的定义,状态定义的一个重要特性就是要确保**“无后效性”。**
很多DP问题在定义状态的时候,为了确保无后效性,需要在状态中加入多个维度,如果每个维度都用一维数组来表示的话,当维度较多时会导致占用的空间太大。
很多时候状态的维度虽然很多,但是决策非常少,特别的很多时候只有两种决策。例如背包问题中每个物品只有0和1两种决策。对于这种情况,没有必要为每个维度都分配一维空间,而是用一个二进制数来存储所有维度,每个二进制位记录一个维度的决策。这种使用二进制对状态进行压缩的DP,称为状态压缩DP。
2.例题
思路:
按照行进行阶段划分,对于每一行中的每个格子做决策,某一行的任何一个格子,如果其是一个
竖着的1 * 2长方形的上半部分,那么它会对下一行的决策产生影响,否则其对下一行没有影响。
分别用1和0来表示格子的两种状态,1表示格子是1 * 2长方形的上半部分,0表示其他情况。
可以用一个M位的二进制数来表示每一行的某一行的格子状态。定义状态d(i, j)表示第i行的状态为j时前i行的分隔方案的总数,j是用十进制记录的M位二进制数。
第i-1行的形态k能转移到第i行的形态j,当且仅当:
k和j执行与位运算(&)的结果是0(两行的同一列不能同为1(都是12长方形的上半部分))。
k和j执行或位运算(|)的结果中,连续0的数量都是偶数个(代表横着的12的方块)。
为了提升性能,可以预处理出[0, 2^M - 1]中所有满足连续0的数量都是偶数的整数集合S。
状态转移方程:d(i, j) = sum(d(i-1, k)); j & k == 0 && j | k ∈ S
初始化:d(i, j) = 0, d(0, 0) = 1;最终答案为:d(N, 0);
时间复杂度:O(N * 2^M * 2^M) = O(N * 4^M);
代码:
#include <bits/stdc++.h>
using namespace std ;
const int N = 12 ;
long long n , m , dp[N][1 << N] ;
bool ok[1 << N] ;
int main ()
{
while (cin >> n >> m && n)
{
memset (ok , 0 , sizeof (ok)) ;
for (int i = 0; i < (1 << m); i++)
{
bool now = true , odd = true ;
for (int j = 0; j < m; j++)
{
if (i & (1 << j)) odd &= now , now = true ;
else now = ! now ;
}
ok[i] = odd & now ;
}
memset (dp , 0 , sizeof (dp)) ;
dp[0][0] = 1 ;
for (int i = 1; i <= n; i++)
{
for (int j = 0; j < (1 << m); j++)