斜率优化DP学习笔记
我是个DP菜鸡实锤了
先来看题
考虑DP,应该很容易可以得出状态转移方程。设
其中
但是朴素计算这个东西的复杂度是
若
将其拆开
将含
这个式子的右边就长得很像斜率了。
而我们发现
因此,我们可以维护一个单调队列,保存所有合法的决策点。在整个过程中,一旦遇到队头两点 pop掉(因为它将永远不会优于点
这样一来,每个点至多入队和出队一次,我们就做到了
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n, m;
int a[500005];
int sum[500005], dp[500005];
int q[500005];
int up(int x, int y) {return dp[y] + sum[y] * sum[y] - dp[x] - sum[x] * sum[x];}//点x和点y组成的斜率的分子
int down(int x, int y) {return sum[y] - sum[x];}//同上,分母
void work() {
memset(dp, 0, sizeof(dp)); memset(q, 0, sizeof(q)); memset(sum, 0, sizeof(sum));
for (int i = 1; i <= n; i++)
cin >> a[i], sum[i] = sum[i - 1] + a[i];
int l = 1, r = 1; q[1] = 0;
for (int i = 1; i <= n; i++) {
while(l + 1 <= r && up(q[l], q[l + 1]) <= 2 * sum[i] * down(q[l], q[l + 1])) l++;//一旦发现推的式子成立,就排除队头决策
int j = q[l];
dp[i] = dp[j] + (sum[i] - sum[j]) * (sum[i] - sum[j]) + m;//这时可以直接取出队头计算了,由于队内斜率的单调性,q[l]一定最优
while (l + 1 <= r && up(q[r - 1], q[r]) * down(q[r], i) >= up(q[r], i) * down(q[r - 1], q[r])) r--;//维护决策单调性
q[++r] = i;//把i入队
}
cout << dp[n] << endl;
}
signed main() {
while (cin >> n >> m)
work();
return 0;
}
把分子和分母单独写一个函数应该会比较清楚吧。
再来看这个
考虑朴素DP:
发现这个式子比较臃肿,可以换个元。设
接下来就是一样的套路。考虑若
由于
Q:何时可以考虑斜率优化DP?
A:状态转移方程遵循如下形式:
其中
一道经典题
这道题挺有意思的,做法很多。设第
设
其中
我们发现
代码稍有不同。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n, m, maxt;
int a[505];
int sum[4000005];
int dp[4000005];
int q[4000005];
int ans = 1e18;
int up(int x, int y) {return dp[y] - dp[x];}
int down(int x, int y) {return sum[y] - sum[x];}
signed main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
sum[++a[i]]++; maxt = max(maxt, a[i]);
}
for (int i = 1; i <= maxt + m; i++)
sum[i] += sum[i - 1];
int l = 1, r = 1; q[1] = 0;
for (int i = 1; i <= maxt + m; i++) {
if (i > m) {
int x = i - m;
while (l + 1 <= r && up(q[r - 1], q[r]) * down(q[r], x) >= up(q[r], x) * down(q[r - 1], q[r])) r--;
q[++r] = x;
}//由于此时i-m也是决策的一个候选项,所以先将i-m入队
while (l + 1 <= r && up(q[l], q[l + 1]) <= i * down(q[l],q[l + 1])) l++;
int j = q[l];
dp[i] = dp[j] + (sum[i] - sum[j]) * i;
if (i >= maxt) ans = min(ans, dp[i]);//注意答案要取最小值
}
for (int i = 1; i <= n; i++)
ans -= a[i];
cout << ans << endl;
return 0;
}
几道练习题:
- P2365 任务安排
- P3628 [APIO2010]特别行动队
- P3648 [APIO2014]序列分割
- P4072 [SDOI2016]征途
没了