dp优化 | 各种dp优化方式例题精选
前言
本文选题都较为基础,仅用于展示优化方式,如果是要找题单而不是看基础概念,请忽略本文。
本文包含一些常见的dp优化(“√”表示下文会进行展示,没“√”表示暂时还咕着):前缀和优化(√)、单调队列优化(√)、斜率优化(√)、四边形不等式优化、数据结构优化……
由于写本文主要是记录蒟蒻的dp优化学习过程,所以可能很不完善,也会有很多错误 (?) 。推荐看巨佬的:【学习笔记】动态规划—各种 DP 优化 - 辰星凌
1. 前缀和优化dp
进行状态转移时,如果发现需加上前面的一类状态,就可以选择使用数组进行累计操作,以达到降维度的效果。
1.1 P1521 求逆序对
1.1.1 题目大意
给出
1.1.2 数据范围
1.1.3 做法
设
考虑在
不难列出转移式:
其中的
同时初始化
由于此题比较水,所以不优化也能过。
const int N = 110, mod = 10000;
int n, k, f[N][N * N >> 1];
int main() {
n = read(), k = read();
f[1][0] = 1;
for (int i = 2; i <= n; i++)
for (int j = 0; j <= (i * (i - 1)) >> 1; j++)
for (int k = 0; k <= min(j, i - 1); k++)
f[i][j] = (f[i][j] + f[i - 1][j - k]) % mod;
printf("%d\n", f[n][k]);
return 0;
}
接下来开始优化。
现在把上面转移的式子改一下,方便优化:
for (int i = 2; i <= n; i++)
for (int j = 0; j <= (i * (i - 1)) >> 1; j++)
for (int k = max(0, j - (i - 1)); k <= j; k++)
f[i][j] = (f[i][j] + f[i - 1][k]) % mod;
开数组
相应的,转移式变为
for (int i = 1; i <= n; i++) f[i][0] = s[i][0] = 1;
for (int i = 2; i <= n; i++) {
for (int j = 1; j <= (i * (i - 1)) >> 1; j++)
s[i - 1][j] = (s[i - 1][j - 1] + f[i - 1][j]) % mod;
for (int j = 1; j <= (i * (i - 1)) >> 1; j++)
f[i][j] = (s[i - 1][j] + mod - ((j - (i - 1) - 1) < 0 ? 0 : s[i - 1][j - (i - 1) - 1])) % mod;
}
注意到
for (int i = 1; i <= n; i++) f[i][0] = 1;
s[0] = 1;
for (int i = 2; i <= n; i++) {
for (int j = 1; j <= (i * (i - 1)) >> 1; j++)
s[j] = (s[j - 1] + f[i - 1][j]) % mod;
for (int j = 1; j <= (i * (i - 1)) >> 1; j++)
f[i][j] = (s[j] + mod - ((j - (i - 1) - 1) < 0 ? 0 : s[j - (i - 1) - 1])) % mod;
}
1.2 P2513 [HAOI2009]逆序对数列
1.2.1 题目大意
给出
1.2.2 数据范围
1.2.3 做法
乍一眼看是不是和上题一模一样。
如果直接提交上题的代码(改了数据范围),就会得到30分的好成绩。(最后几个点全部MLE)
稍稍计算一下,就会发现 int
数组是不是有那么亿点点大?
那么如何优化代码呢?
注意到上题的代码中,逆序对数枚举的上限为
不难想到改成以下代码:
const int N = 1010, mod = 10000;
int n, k, f[N][N], s[N ];
int main() {
n = read(), k = read();
for (int i = 1; i <= n; i++) f[i][0] = 1;
s[0] = 1;
for (int i = 2; i <= n; i++) {
for (int j = 1; j <= min((i * (i - 1)) >> 1, k); j++)
s[j] = (s[j - 1] + f[i - 1][j]) % mod;
for (int j = 1; j <= min((i * (i - 1)) >> 1, k); j++)
f[i][j] = (s[j] + mod - ((j - (i - 1) - 1) < 0 ? 0 : s[j - (i - 1) - 1])) % mod;
}
printf("%d\n", f[n][k]);
return 0;
}
真好,既优化了空间又优化了时间。
2. 单调队列优化dp
借助单调队列的单调性,及时排除不可能的决策,保持候选集合的高度有效性和秩序性。
单调队列尤其适合优化决策取值范围的上、下界均单调变化,每个决策在候选集合中插入或删除至多一侧的问题。
2.1 P1440 求m区间内的最小值
2.1.1 题目大意
给定一个长度为
2.1.2 数据范围
2.1.3 做法
好像和单调队列优化dp没什么关系?
此题用于体验单调队列,就不多写了,直接用单调队列模拟操作即可。
const int N = 2000010;
int n, m, s[N], l = 1, r, a[N];
int main() {
n = read(), m = read();
printf("0\n");
for (int i = 1; i <= n - 1; i++) {
a[i] = read();
while (r >= l && a[s[r]] > a[i]) r--;
s[++r] = i;
while (s[r] - s[l] + 1 > m && l <= r) l++;
printf("%d\n", a[s[l]]);
}
return 0;
}
2.2 P5858 「SWTR-03」Golden Sword
2.2.1 题目大意
有
进行
- 取出不超过
个物品。 - 放入物品
。
其中容器最多容纳
每次操作会产生
求
2.2.2 数据范围
2.2.3 做法
设
那么
其中
于是就得到了一个45分做法(long long没开全只有35)
const int N = 5010;
const ll INF = 1e18;
int n, w, s;
ll f[N][N], ans = -INF, a[N];
int main() {
n = read(), w = read(), s = read();
for (int i = 1; i <= n; i++) a[i] = read();
for (int i = 0; i <= n; i++)
for (int j = 0; j <= w; j++)
f[i][j] = -INF;
f[0][0] = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= w; j++)
for (int k = j - 1; k <= min(w, j - 1 + s); k++)
f[i][j] = max(f[i][j], f[i - 1][k] + a[i] * j);
for (int i = 0; i <= w; i++) ans = max(ans, f[n][i]);
printf("%lld\n", ans);
return 0;
}
(不如先动手写个部分分做法?)
考虑优化。先把式子变一下:虽然说这么一提好像不能优化什么,你会发现,
const int N = 5010;
const ll INF = 1e18;
int n, w, s;
ll f[N][N], ans = -INF, a[N];
int ss[N];
int main() {
n = read(), w = read(), s = read();
for (int i = 1; i <= n; i++) a[i] = read();
for (int i = 0; i <= n; i++)
for (int j = 0; j <= w; j++)
f[i][j] = -INF;
f[0][0] = 0;
for (int i = 1; i <= n; i++) {
int l = 1, r = 0;
ss[++r] = w;
for (int j = w; j; j--) {
while (f[i - 1][ss[r]] < f[i - 1][j - 1] && r >= l) r--;
ss[++r] = j - 1;
while ((ss[l] - ss[r] + 1) - 1 > s && l <= r) l++;
f[i][j] = f[i - 1][ss[l]] + j * a[i];
}
}
for (int i = 0; i <= w; i++) ans = max(ans, f[n][i]);
printf("%lld\n", ans);
return 0;
}
3. 斜率优化dp
3.1 P3195 [HNOI2008]玩具装箱
3.1.1 题目大意
有
现需把这些物品压缩进一些容器里,制作一个容器的花费为
每个容器中的物品编号需要是连续的,而将编号
求压缩完所有物品所需的总花费的最小值。
3.1.2 数据范围
3.1.3 做法
设
令
移项得:
令
把式子展开再合并:
令:
发现原式转化为
看上去有那么亿点点的像
考虑这个求
实际上,只需维护下凸壳的那些点。
对于本题,
const int N = 50010;
int n, c[N], l = 1, r = 0;;
ll sum[N], s[N], f[N], L;
ll Get(int x) {
return f[x] + (sum[x] + L) * (sum[x] + L);
}
long double slope(int x, int y) {
return (Get(y) - Get(x)) * 1.0 / (sum[y] - sum[x]);
}
int main() {
n = read(), L = read() + 1;
for (int i = 1; i <= n; i++) c[i] = read();
for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + c[i] + 1;
s[++r] = 0;
for (int i = 1; i <= n; i++) {
while (l < r && slope(s[l], s[l + 1]) <= (sum[i] << 1)) l++;
f[i] = f[s[l]] + (sum[i] - sum[s[l]] - L) * (sum[i] - sum[s[l]] - L);
while (l <= r && slope(s[r - 1], s[r]) >= slope(s[r - 1], i)) r--;
s[++r] = i;
}
printf("%lld\n", f[n]);
return 0;
}
N. 参考内容
本文作者:shiranui
本文链接:https://www.cnblogs.com/shiranui/p/16787371.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步