动态规划专题-专项训练:斜率优化 DP
1. 前言
本篇博文是斜率优化 DP 的习题博文。
讲解斜率优化 DP 的博文的传送门:动态规划专题-学习笔记:斜率优化 DP
说句实在话,斜率优化的题目只要推出转移方程, y = k x + b y=kx+b y=kx+b 还不好推吗?
注意本文全是单调队列斜率优化,如果想看一般的李超线段树斜率优化的看这篇:数据结构专题-学习笔记:李超线段树。
2. 练习题
题单:
P4360 [CEOI2004]锯木厂选址
注意以下叙述对所有数据做了一个翻转处理。
设 f i f_i fi 表示当前从 1 到 i i i, i i i 作为一个锯木厂点的最小花费。
考虑枚举另外一个锯木厂点 j < i j<i j<i,那么有转移方程:
f i = min { t o t − d j × ( w i − 1 − w j − 1 ) − d i × ( w n − w i − 1 ) } f_i=\min\{tot-d_j\times (w_{i-1}-w_{j-1})-d_i\times (w_n-w_{i-1})\} fi=min{tot−dj×(wi−1−wj−1)−di×(wn−wi−1)}
其中的 d , w d,w d,w 做了前缀和处理, t o t tot tot 表示将所有木头移到第 0 个点的花费。
然后就是斜率优化拆拆拆,码码码。
代码:
/*
========= Plozia =========
Author:Plozia
Problem:P4360 [CEOI2004]锯木厂选址
Date:2021/4/14
========= Plozia =========
*/
#include <bits/stdc++.h>
typedef long long LL;
const int MAXN = 20000 + 10;
int n, w[MAXN], d[MAXN], tot, sumw[MAXN], sumd[MAXN], ans = 0x7f7f7f7f, f[MAXN];
int q[MAXN], l, r;
int read()
{
int sum = 0, fh = 1; char ch = getchar();
for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = (sum << 3) + (sum << 1) + (ch ^ 48);
return sum * fh;
}
int Min(int fir, int sec) { return (fir < sec) ? fir : sec; }
int Max(int fir, int sec) { return (fir > sec) ? fir : sec; }
int X(int x) { return sumd[x]; }
int Y(int x) { return sumd[x] * sumw[x - 1]; }
int K(int x) { return sumw[x - 1]; }
double Slope(int x, int y) { return ((double)Y(x) - Y(y)) / ((double)X(x) - X(y)); }
int main()
{
memset(f, 0x7f, sizeof(f));
n = read();
for (int i = n; i >= 1; --i) w[i] = read(), d[i] = read();
for (int i = 1; i <= n; ++i) sumw[i] = sumw[i - 1] + w[i], sumd[i] = sumd[i - 1] + d[i];
for (int i = 1; i <= n; ++i) tot += w[i] * sumd[i];
q[l = r = 1] = 1;
for (int i = 2; i <= n; ++i)
{
while (l < r && Slope(q[l], q[l + 1]) <= (double)K(i)) ++l;
int j = q[l]; f[i] = tot - sumd[j] * (sumw[i - 1] - sumw[j - 1]) - sumd[i] * (sumw[n] - sumw[i - 1]);
while (l < r && Slope(i, q[r]) <= Slope(q[r], q[r - 1])) --r;
q[++r] = i;
}
// for (int i = 2; i <= n; ++i)
// for (int j = 1; j < i; ++j)
// f[i] = Min(f[i], tot - sumd[j] * (sumw[i - 1] - sumw[j - 1]) - sumd[i] * (sumw[n] - sumw[i - 1]));
for (int i = 2; i <= n; ++i) ans = Min(ans, f[i]);
// for (int i = 2; i <= n; ++i) std::cout << f[i] << "\n";
printf("%d\n", ans); return 0;
}
P3195 [HNOI2008]玩具装箱
设 f i f_i fi 表示处理了 [ 1 , i ] [1,i] [1,i] 的所有玩具时的最小总费用。
那么有状态转移方程: f i = min { f j + ( i − j − 1 + c i − c j − l ) 2 ∣ j < i } f_i=\min\{f_j+(i-j-1+c_i-c_j-l)^2|j<i\} fi=min{fj+(i−j−1+ci−cj−l)2∣j<i}
同样的, c c c 做了前缀和处理。
然后还是斜率优化拆拆拆,码码码。
代码:
/*
========= Plozia =========
Author:Plozia
Problem:P3195 [HNOI2008]玩具装箱
Date:2021/4/15
========= Plozia =========
*/
#include <bits/stdc++.h>
typedef long long LL;
const int MAXN = 5e4 + 10;
int n, L, l, r, q[MAXN];
LL f[MAXN], c[MAXN];
int read()
{
int sum = 0, fh = 1; char ch = getchar();
for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = (sum << 3) + (sum << 1) + (ch ^ 48);
return sum * fh;
}
LL Min(LL fir, LL sec) { return (fir < sec) ? fir : sec; }
LL Max(LL fir, LL sec) { return (fir > sec) ? fir : sec; }
double K(int i) { return 2.0 * (i + c[i]); }
double X(int i) { return 1.0 * i + c[i]; }
double Y(int i) { return 1.0 * f[i] + (i + c[i]) * (i + c[i]) + 2 * (i + c[i]) * (L + 1); }
double Slope(int x, int y) { return (Y(x) - Y(y)) / (X(x) - X(y)); }
int main()
{
n = read(), L = read();
for (int i = 1; i <= n; ++i) c[i] = read() + c[i - 1];
q[l = r = 1] = 0;
for (int i = 1; i <= n; ++i)
{
while (l < r && Slope(q[l], q[l + 1]) <= K(i)) ++l;
int j = q[l]; f[i] = f[j] + (i - j - 1 + c[i] - c[j] - L) * (i - j - 1 + c[i] - c[j] - L);
while (l < r && Slope(q[r], i) <= Slope(q[r], q[r - 1])) --r;
q[++r] = i;
}
// for (int i = 1; i <= n; ++i)
// for (int j = 0; j < i; ++j)
// f[i] = Min(f[i], f[j] + (i - j - 1 + c[i] - c[j] - l) * (i - j - 1 + c[i] - c[j] - l));
printf("%lld\n", f[n]); return 0;
}
3. 总结
斜率优化的题目就是推暴力的方程,没了~
当然有一部分题目还是很难的,仅斜率优化可能不够,就需要一些科技来进一步优化。