【笔记】状态定义优化DP 和 单调队列优化DP
定义
状态定义优化DP:
如果 DP 状态的一维,可以用其他维的数据表示,则该维可以删去。
通过单调队列这一数据结构优化 DP 的状态转移。
单调队列非常适合优化 决策取值范围的上、下界随枚举阶段单调变化,同时每个决策在候选集合中插入或删除至多一次的 DP 最优化问题。
例题
状态定义优化DP:
乌龟棋 (NOI 2010 提高组)
思路:
原本状态定义为:\(dp_{i,a,b,c,d}\) 表示在第 \(i\) 格且用了 \(a\) 张1号卡牌、\(b\) 张2号卡牌、\(c\) 张3号卡牌、\(d\) 张4号卡牌的最高得分。
此时状态转移方程为:
这样定义状态在 空间复杂度 和 时间复杂度 上都无法支持。
但是,原状态定义的第一维 \(i\),可以通过 \(a, b, c, d\) 计算得出,即 $i = a \times 1 + b \times 2 + c \times 3 + d \times 4 {\color{Red} + 1} $。
注意:在棋盘上是从第1格开始走的,所以要 \({\color{Red} + 1}\)。
所以可将状态定义优化为 \(dp_{a,b,c,d}\) 表示在第 \((a \times 1 + b \times 2 + c \times 3 + d \times 4)\) 格且用了 \(a\) 张1号卡牌、\(b\) 张2号卡牌、\(c\) 张3号卡牌、\(d\) 张4号卡牌的最高得分。
则状态转移方程为:
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 400, M = 45;
int n, m;
int a[N], b[5];
int dp[M][M][M][M];
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
for (int i = 1; i <= m; i ++ )
{
int x; scanf("%d", &x);
b[x] ++ ; // 用 b[i] 记录第 i 种卡牌的数量
}
memset(dp, -0x3f, sizeof dp); // 初始化为负无穷
dp[0][0][0][0] = a[1]; // 赋初值,一开始不用卡牌,在第1格开始
for (int i = 0; i <= b[1]; i ++ ) // 使用了 i 张1号卡牌
for (int j = 0; j <= b[2]; j ++ ) // 使用了 j 张2号卡牌
for (int k = 0; k <= b[3]; k ++ ) // 使用了 k 张3号卡牌
for (int v = 0; v <= b[4]; v ++ ) // 使用了 v 张4号卡牌
{
int pos = i + j * 2 + k * 3 + v * 4 + 1; // 使用完卡牌的位置
if (i > 0)
dp[i][j][k][v] = max(dp[i][j][k][v], dp[i - 1][j][k][v] + a[pos]);
if (j > 0)
dp[i][j][k][v] = max(dp[i][j][k][v], dp[i][j - 1][k][v] + a[pos]);
if (k > 0)
dp[i][j][k][v] = max(dp[i][j][k][v], dp[i][j][k - 1][v] + a[pos]);
if (v > 0)
dp[i][j][k][v] = max(dp[i][j][k][v], dp[i][j][k][v - 1] + a[pos]);
}
printf("%d\n", dp[b[1]][b[2]][b[3]][b[4]]); // 输出答案
return 0;
}
单调队列优化DP:
经过几个月辛勤的工作,FJ决定让奶牛放假。假期可以在1…N天内任意选择一段(需要连续),每一天都有一个享受指数W。但是奶牛的要求非常苛刻,假期不能短于P天,否则奶牛不能得到足够的休息;假期也不能超过Q天,否则奶牛会玩的腻烦。FJ想知道奶牛们能获得的最大享受指数。
思路:
令 \(sum_i\) 表示从第1天到第 \(i\) 天的总享受指数,即前缀和。
如果选定 \(l, r\),那么享受指数则为 \(sum_r - sum_{l - 1}\)。
因为题目要求 \(p \le 假期长度 \le q\)。
所以枚举 \(r\),则 \(l\) 的取值范围为 $\left [ r - p + 1, r - q + 1 \right ] $。
因此两端点的变化满足单调性,所以可以用单调队列优化
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 5;
int n, p, q;
LL a[N];
deque<LL> qe; // 单调队列,从小到大
int main()
{
scanf("%d%d%d", &n, &p, &q);
for (int i = 1; i <= n; i ++ )
{
scanf("%lld", &a[i]);
a[i] += a[i - 1]; // 预处理前缀和
}
LL ans = -0x3f3f3f3f;
for (int i = p; i <= n; i ++ )
{
while (!qe.empty() && a[qe.back()] >= a[i - p]) qe.pop_back(); // 为满足队列单调性,将 在队尾且影响单调性 的元素删去
qe.push_back(i - p); // 插入当前元素
while (!qe.empty() && qe.front() + q < i) qe.pop_front(); // 将队头已经“过期”的元素删去
ans = max(ans, a[i] - a[qe.front()]); // 在所有答案中寻找最大值
}
printf("%lld\n", ans); // 输出答案
return 0;
}

浙公网安备 33010602011771号