【总结】DP的常用优化(2/5)
1. 单调队列优化
1.1. 引入——单调队列
滑动窗口
很显然我们可以用 ST表或线段树做到
对于两点
1.2. 算法介绍
“当一个选手比你小还比你强,你就打不过他了”。这是对单调队列非常形象的概括。如果一个决策点不可能成为最优决策点,就将它排出。
类似滑动窗口,我们可以利用单调队列优化以下一类的
1.3. 例题
I. 修剪草坪[USACO 2011 Open Gold]
我们可以定义状态
我们可以发现对与第二种,
#include <cstdio>
#include <iostream>
#define int long long
using namespace std;
const int MAXN = 1e5 + 5;
int n, k;
int head, tail;
int a[MAXN], sum[MAXN];
int dp[MAXN][2];
int q[MAXN];
signed main() {
scanf("%lld %lld", &n, &k);
for (int i = 1; i <= n; i++)
scanf("%lld", &a[i]), sum[i] = sum[i - 1] + a[i];
head = tail = 1;
for (int i = 1; i <= n; i++) {
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1]);
while (head <= tail && q[head] < i - k)
head++;
dp[i][1] = dp[q[head]][0] + sum[i] - sum[q[head]];
while (head <= tail && dp[q[tail]][0] - sum[q[tail]] < dp[i][0] - sum[i])
tail--;
q[++tail] = i;
}
printf("%lld", max(dp[n][0], dp[n][1]));
return 0;
}
2. 斜率优化
2.1. 算法介绍
如果一类
我们假设
2.2. 例题
I. 打印文章[HDU 3507]
我们很容易写出状态转移方程
对这个式子进行展开,一项得到
就可以推出:
就可以进行斜率优化了
#include <cstdio>
#include <algorithm>
#include <cstring>
#define int long long
using namespace std;
const int MAXN = 5e5 + 5, INF = 1e18;
int n, m;
int a[MAXN], sum[MAXN];
int dp[MAXN];
int q[MAXN], head, tail;
long double slope(int x, int y) {
if (sum[x] == sum[y])
return (dp[x] + sum[x] * sum[x] >= 0) ? 1e9 : -1e9;
return 1.0 * (dp[x] + sum[x] * sum[x] - dp[y] - sum[y] * sum[y]) / (sum[x] - sum[y]);
}
signed main() {
while (~scanf("%lld %lld", &n, &m)) {
for (int i = 1; i <= n; i++)
scanf("%lld", &a[i]), sum[i] = sum[i - 1] + a[i], dp[i] = INF;
head = 1, tail = 1;
q[1] = 0;
for (int i = 1; i <= n; i++) {
while (head < tail && slope(q[head], q[head + 1]) <= 2 * sum[i])
head++;
int p = q[head];
dp[i] = dp[p] + (sum[i] - sum[p]) * (sum[i] - sum[p]) + m;
while (head < tail && slope(q[tail - 1], q[tail]) >= slope(q[tail], i))
tail--;
q[++tail] = i;
}
printf("%lld\n", dp[n]);
}
return 0;
}
II. 玩具装箱
由题意可以得
#include <cstdio>
#include <iostream>
#include <cstring>
#define int long long
using namespace std;
const int MAXN = 5e4 + 5;
int n, L;
int sum[MAXN], dp[MAXN];
int q[MAXN], head, tail;
double slope(int i, int j) {
return 1.0 * (dp[i] + (sum[i] + i + 1 + L) * (sum[i] + i + 1 + L) - dp[j] - (sum[j] + j + 1 + L) * (sum[j] + j + 1 + L)) / (sum[i] + i - sum[j] - j);
}
signed main() {
scanf("%lld %lld", &n, &L);
for (int i = 1; i <= n; i++)
scanf("%lld", &sum[i]), sum[i] += sum[i - 1];
head = tail = 1;
q[1] = 0;
for (int i = 1; i <= n; i++) {
while (head < tail && slope(q[head], q[head + 1]) <= 2 * (sum[i] + i))
head++;
int p = q[head];
dp[i] = dp[p] + (sum[i] - sum[p] + i - p - 1 - L) * (sum[i] - sum[p] + i - p - 1 - L);
while (head < tail && slope(q[tail - 1], q[tail]) >= slope(q[tail], i))
tail--;
q[++tail] = i;
}
printf("%lld", dp[n]);
return 0;
}
III. 任务安排
发现一组任务的完成时间依赖于分成的组数,很难受,那么使用 费用提前计算 的技巧,即将启动时间
得到的式子显然可以进行斜率优化,但是
#include <cstdio>
#define int long long
const int MAXN = 3e5 + 5, INF = 1e9;
int n, s;
int sumt[MAXN], sumc[MAXN];
int dp[MAXN];
int q[MAXN], head, tail;
int getx(int i, int j) {
return sumc[i] - sumc[j];
}
int gety(int i, int j) {
return dp[i] - dp[j];
}
int find(int i) {
if (head == tail)
return q[head];
int l = head, r = tail;
while (l < r) {
int mid = (l + r) >> 1;
if (gety(q[mid + 1], q[mid]) <= (s + sumt[i]) * getx(q[mid + 1], q[mid]))
l = mid + 1;
else
r = mid;
}
return q[l];
}
signed main() {
scanf("%lld %lld", &n, &s);
for (int i = 1; i <= n; i++)
scanf("%lld %lld", &sumt[i], &sumc[i]), sumt[i] += sumt[i - 1], sumc[i] += sumc[i - 1];
q[head = tail = 1] = 0;
for (int i = 1; i <= n; i++) {
int p = find(i);
dp[i] = dp[p] + sumt[i] * (sumc[i] - sumc[p]) + s * (sumc[n] - sumc[p]);
while (head < tail && gety(q[tail - 1], q[tail]) * getx(q[tail], i) >= gety(q[tail], i) * getx(q[tail - 1], q[tail]))
tail--;
q[++tail] = i;
}
printf("%lld", dp[n]);
return 0;
}
IV. 土地购买[Usaco2008 Mar]
考虑这样的两个矩形,矩形
我们可以得到状态转移
进行斜率优化即可。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#define int long long
using namespace std;
const int MAXN = 5e4 + 5, INF = 1e18;
int n;
int dp[MAXN];
int q[MAXN], head, tail;
struct Node {
int x, y;
bool operator < (const Node o) {
return x == o.x ? y < o.y : x < o.x;
}
} a[MAXN], b[MAXN];
int top;
signed main() {
scanf("%lld", &n);
for (int i = 1; i <= n; i++)
scanf("%lld %lld", &a[i].x, &a[i].y);
sort(a + 1, a + n + 1);
b[0].y = INF;
for (int i = 1; i <= n; i++) {
while (top && b[top].y < a[i].y)
top--;
b[++top] = a[i];
}
head = tail = 1;
for (int i = 1; i <= top; i++) {
while (head < tail && (dp[q[head + 1]] - dp[q[head]]) <= b[i].x * (b[q[head] + 1].y - b[q[head + 1] + 1].y))
head++;
int p = q[head];
dp[i] = dp[p] + b[p + 1].y * b[i].x;
while (head < tail && (dp[q[tail]] - dp[q[tail - 1]]) * (b[q[tail] + 1].y - b[i + 1].y) >= (dp[i] - dp[q[tail]]) * (b[q[tail - 1] + 1].y - b[q[tail] + 1].y))
tail--;
q[++tail] = i;
}
printf("%lld", dp[top]);
return 0;
}
V. 仓库建设
容易得到状态转移方程
利用前缀和
进行斜率优化即可。
#include <cstdio>
#include <iostream>
#include <cstring>
#define int long long
using namespace std;
const int MAXN = 1e6 + 5, INF = 1e18;
int n;
int d[MAXN], sump[MAXN], c[MAXN], sum[MAXN];
int dp[MAXN];
int q[MAXN], head, tail;
signed main() {
scanf("%lld", &n);
for (int i = 1; i <= n; i++)
scanf("%lld %lld %lld", &d[i], &sump[i], &c[i]), sum[i] = sum[i - 1] + d[i] * sump[i], sump[i] += sump[i - 1];
for (int i = 1; i <= n; i++) {
while (head < tail && (dp[q[head + 1]] + sum[q[head + 1]] - dp[q[head]] - sum[q[head]]) <= d[i] * (sump[q[head + 1]] - sump[q[head]]))
head++;
int p = q[head];
dp[i] = dp[p] + d[i] * (sump[i] - sump[p]) - (sum[i] - sum[p]) + c[i];
while (head < tail && (dp[q[tail]] + sum[q[tail]] - dp[q[tail - 1]] - sum[q[tail - 1]]) * (sump[i] - sump[q[tail]]) >= (dp[i] + sum[i] - dp[q[tail]] - sum[q[tail]]) * (sump[q[tail]] - sump[q[tail - 1]]))
tail--;
q[++tail] = i;
}
int ans = INF;
for (int i = n; i >= 1; i--) {
ans = min(ans, dp[i]);
if (sump[i] - sump[i - 1])
break;
}
printf("%lld", ans);
return 0;
}
VII. Cats Transport
#include <cstdio>
#include <algorithm>
#include <cstring>
#define int long long
using namespace std;
const int MAXN = 2e5 + 5, MAXM = 1e5 + 5, MAXP = 105, INF = 1e18;
int n, m, p;
int d[MAXN];
int h[MAXN], t[MAXN], a[MAXN], s[MAXN];
int dp[MAXP][MAXM];
int q[MAXN], head, tail;
signed main() {
scanf("%lld %lld %lld", &n, &m, &p);
for (int i = 2; i <= n; i++)
scanf("%lld", &d[i]), d[i] += d[i - 1];
for (int i = 1; i <= m; i++) {
int h, t;
scanf("%lld %lld", &h, &t), a[i] = t - d[h];
}
sort(a + 1, a + m + 1);
for (int i = 1; i <= m; i++)
s[i] = s[i - 1] + a[i];
memset(dp, 63, sizeof(dp));
dp[0][0] = 0;
for (int i = 1; i <= p; i++) {
head = tail = 1;
q[1] = 0;
for (int j = 1; j <= m; j++) {
while (head < tail && (dp[i - 1][q[head + 1]] + s[q[head + 1]] - dp[i - 1][q[head]] - s[q[head]]) <= a[j] * (q[head + 1] - q[head]))
head++;
int p = q[head];
dp[i][j] = min(dp[i - 1][j], dp[i - 1][p] + a[j] * (j - p) - (s[j] - s[p]));
while (head < tail && (dp[i - 1][q[tail]] + s[q[tail]] - dp[i - 1][q[tail - 1]] - s[q[tail - 1]]) * (j - q[tail]) >= (dp[i - 1][j] + s[j] - dp[i - 1][q[tail]] - s[q[tail]]) * (q[tail] - q[tail - 1]))
tail--;
q[++tail] = j;
}
}
printf("%lld", dp[p][m]);
return 0;
}
VIII. 柠檬
推出式子
因为我们需要维护一个上凸壳,因为
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#define int unsigned long long
using namespace std;
const int MAXN = 1e5 + 5, INF = 1e18;
int n;
int s[MAXN], c[MAXN];
int dp[MAXN];
int mp[MAXN];
int getx(int i, int j) {
return s[i] * c[i] - s[j] * c[j];
}
int gety(int i, int j) {
return (dp[i - 1] + s[i] * c[i] * c[i] - 2 * s[i] * c[i]) - (dp[j - 1] + s[j] * c[j] * c[j] - 2 * s[j] * c[j]);
}
vector<int> v[MAXN];
signed main() {
scanf("%llu", &n);
for (int i = 1; i <= n; i++)
scanf("%llu", &s[i]), c[i] = ++mp[s[i]];
for (int i = 1; i <= n; i++) {
while (v[s[i]].size() >= 2 && gety(v[s[i]][v[s[i]].size() - 1], v[s[i]][v[s[i]].size() - 2]) * getx(i, v[s[i]][v[s[i]].size() - 1]) <= gety(i, v[s[i]][v[s[i]].size() - 1]) * getx(v[s[i]][v[s[i]].size() - 1], v[s[i]][v[s[i]].size() - 2]))
v[s[i]].pop_back();
v[s[i]].push_back(i);
while (v[s[i]].size() >= 2 && gety(v[s[i]][v[s[i]].size() - 1], v[s[i]][v[s[i]].size() - 2]) <= 2 * c[i] * getx(v[s[i]][v[s[i]].size() - 1], v[s[i]][v[s[i]].size() - 2]))
v[s[i]].pop_back();
int p = v[s[i]][v[s[i]].size() - 1];
dp[i] = dp[p - 1] + s[i] * (c[i] - c[p] + 1) * (c[i] - c[p] + 1);
}
printf("%llu", dp[n]);
return 0;
}
IX. 锯木厂选址
设
#include <cstdio>
#include <algorithm>
#define int long long
using namespace std;
const int MAXN = 2e5 + 5, INF = 1e18;
int n, ans = INF;
int w[MAXN], d[MAXN], s[MAXN], sum;
int q[MAXN], head, tail;
double slope(int i, int j) {
return 1.0 * (d[i] * s[i] - d[j] * s[j]) / (s[i] - s[j]);
}
signed main() {
scanf("%lld", &n);
for (int i = 1; i <= n; i++)
scanf("%lld %lld", &w[i], &d[i]);
for (int i = n; i >= 1; i--)
d[i] += d[i + 1];
for (int i = 1; i <= n; i++)
s[i] = s[i - 1] + w[i], sum += d[i] * w[i];
for (int i = 1; i <= n; i++) {
while (head < tail && slope(q[head + 1], q[head]) > d[i])
head++;
int p = q[head];
ans = min(ans, sum - d[p] * s[p] - d[i] * (s[i] - s[p]));
while (head < tail && slope(q[tail - 1], q[tail]) < slope(q[tail], i))
tail--;
q[++tail] = i;
}
printf("%lld", ans);
return 0;
}
X. 征途
利用方差的定义以及
#include <cstdio>
#include <algorithm>
#include <cstring>
#define int long long
using namespace std;
const int MAXN = 3e3 + 5, INF = 1e9;
int n, m;
int a[MAXN], sum[MAXN];
int dp[MAXN], f[MAXN];
int q[MAXN], head, tail;
double slope(int x, int y) {
return 1.0 * (f[y] - f[x] + sum[y] * sum[y] - sum[x] * sum[x]) / (sum[y] - sum[x]);
}
signed main() {
scanf("%lld %lld", &n, &m);
for (int i = 1; i <= n; i++)
scanf("%lld", &a[i]), sum[i] = sum[i - 1] + a[i], f[i] = sum[i] * sum[i];
for (int i = 2; i <= m; i++) {
head = 1, tail = 1;
q[1] = 0;
for (int j = 1; j <= n; j++) {
while (head < tail && slope(q[head], q[head + 1]) < 2 * sum[j])
head++;
int p = q[head];
dp[j] = f[p] + (sum[j] - sum[p]) * (sum[j] - sum[p]);
while (head < tail && slope(q[tail], q[tail - 1]) > slope(q[tail], j))
tail--;
q[++tail] = j;
}
for (int j = 1; j <= n; j++)
f[j] = dp[j];
}
printf("%lld", m * dp[n] - sum[n] * sum[n]);
return 0;
}
本文来自博客园,作者:zhou_ziyi,转载请注明原文链接:https://www.cnblogs.com/zhouziyi/p/DP_optimization_method.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具