斜率优化dp复习笔记
P3648 [APIO2014] 序列分割
将序列分割成 \(k+1\) 个部分,每个部分的元素 \(\geq 1\),然后假设一个块你要分成两个块,代价为分成后两个块里面的元素和的乘积,问你怎么才能使代价最大,并输出方案(SPJ)。
题目分析
注意到顺序是无关紧要的:
若一个块 \(sum\) 要从 \(i,j\) 这里下手(分成三段的和 \(a,b,c\)),顺序所导致的有:
- 先从 \(i\) 砍,代价为 \(a(b+c)+bc=ab+ac+bc\)。
- 先从 \(j\) 砍,代价为 \((a+b)c+ab=ab+ac+bc\)。
两者相等,顺序无用。
故设 \(f_{i,j}\) 表示前 \(i\) 个部分切 \(j\) 次,那么有:
顺序无关,所以我先在 \(i\) 切,然后再切前面的。
考虑斜率优化,对于 \(j<l\) 但 \(j\) 比 \(l\) 更优,并设 \(f_{x,j-1}=f_x\)。
有:
那么点集就是 \((-sum_x,f_x-sum_x^2)\)。
那么对于当前点 \(i\),所有斜率 \(\leq sum_i\) 的都不可行。
然后队头就是最优决策点,为什么呢?因为对于 \(j<l\) 满足即可,显然是第一个。
也就是直接去维护斜率递增。
现在要把 \(i\) 也添加进来。那么我们维护斜率递增即可。
代码
时间复杂度 \(\mathcal{O}(nk)\),代码如下:
#include <iostream>
#include <cstdio>
#include <stdlib.h>
#include <cstring>
#include <stdlib.h>
#include <vector>
#include <deque>
#define int long long
#define N 100005
#define K 205
using namespace std;
int pre[N][K],a[N],sum[N],f[N],g[N];
int x(int id) {return -sum[id];}
int y(int id) {return g[id] - sum[id] * sum[id];}
double slope(int fir,int sec) {
if (x(fir) == x(sec)) return -1e18;
return 1.0 * (y(fir) - y(sec)) / (1.0 * (x(fir) - x(sec)));
}
int q[N],head,tail;
signed main(){
int n,k;
cin >> n >> k;
// k ++;
for (int i = 1;i <= n;i ++) scanf("%lld",&a[i]),sum[i] = sum[i - 1] + a[i];
// for (int i = 0;i <= n;i ++)
// for (int j = 0;j <= n;j ++) f[i][j] = -1e18;
// f[0][0] = 0;
// for (int i = 1;i <= n;i ++)
// for (int j = 1;j <= min(i,k);j ++)
// for (int t = 0;t < i;t ++)
// if (f[t][j - 1] + sum[t] * (sum[i] - sum[t]) > f[i][j])
// f[i][j] = f[t][j - 1] + sum[t] * (sum[i] - sum[t]),pre[i][j] = t;
// printf("%lld\n",f[n][k]);
// int now = n;
// for (int j = k;j > 1;j --) printf("%lld ",pre[now][j]),now = pre[now][j];
for (int t = 1;t <= k;t ++) {
for (int i = 1;i <= n;i ++) g[i] = f[i];
tail = 0;
head = 1;
// q[++tail] = 0;
for (int i = 1;i <= n;i ++) {
while(tail > head && slope(q[head],q[head + 1]) <= sum[i]) head ++;
f[i] = 0;
if (head <= tail) {
pre[i][t] = q[head];
f[i] = g[q[head]] + sum[q[head]] * (sum[i] - sum[q[head]]);
}
while(tail > head && slope(q[tail - 1],q[tail]) >= slope(q[tail],i)) tail --;
q[++tail] = i;
}
}
printf("%lld\n",f[n]);
int now = n;
for (int j = k;j >= 1;j --) printf("%lld ",pre[now][j]),now = pre[now][j];
return 0;
}
P4655 [CEOI 2017] Building Bridges
题目概述
有 \(n\) 根柱子依次排列,每根柱子都有一个高度。第 \(i\) 根柱子的高度为 \(h_i\)。
现在想要建造若干座桥,如果一座桥架在第 \(i\) 根柱子和第 \(j\) 根柱子之间,那么需要 \((h_i-h_j)^2\) 的代价。
在造桥前,所有用不到的柱子都会被拆除,因为他们会干扰造桥进程。第 \(i\) 根柱子被拆除的代价为 \(w_i\),注意 \(w_i\) 不一定非负,因为可能政府希望拆除某些柱子。
现在政府想要知道,通过桥梁把第 \(1\) 根柱子和第 \(n\) 根柱子连接的最小代价。注意桥梁不能在端点以外的任何地方相交。
对于 \(100\%\) 的数据,有 \(2\le n\le 10^5;0\le h_i,\vert w_i\vert\le 10^6\)。
分析
设 \(f_i\) 表示前 \(i\) 个的最小代价。
那么 \(f_1=0\)。
显然有:
那么我们有 \(\mathcal{O}(n^2)\) 代码,期望可得 \(25\) 分:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <stdlib.h>
#include <algorithm>
#include <vector>
#define int long long
#define sqr(x) (x) * (x)
#define N 100005//完了,上瘾了
using namespace std;
int n,h[N],w[N],f[N],sumh[N],sumw[N];
signed main(){
cin >> n;
for (int i = 1;i <= n;i ++) scanf("%lld",&h[i]),sumh[i] = sumh[i - 1] + h[i];
for (int i = 1;i <= n;i ++) scanf("%lld",&w[i]),sumw[i] = sumw[i - 1] + w[i];
for (int i = 1;i <= n;i ++) f[i] = 1e18;
f[1] = 0;
for (int i = 1;i <= n;i ++)
for (int j = 1;j < i;j ++)
f[i] = min(f[i],f[j] + sqr(h[i] - h[j]) + sumw[i - 1] - sumw[j]);
cout << f[n];
return 0;
}
开始斜率优化。
设 \(j<l\) 但是 \(j\) 比 \(l\) 菜。
有:
移项得到:
这里我要提一嘴:当我们左边的那个 \(h_j-h_l\) 不确定是正是负可以通过二分确定继续用单调队列(不删除队头),但是如果连 \(h_i\) 都不是单调的,要用 \(cdq\) 分治或者李超线段树去解决了。
李超线段树你只需要考虑插入一个直线然后在 \(x=h_i\) 时取最小值就行了,有时候挺好写的(这道题也是)。
考虑 \(cdq\) 分治的过程:
解决完 \([l,mid]\)。
解决完 \([mid + 1,r]\)。
通过 \([l,mid]\) 和 \([mid + 1,r]\) 的一些带来的信息来解决 \([l,r]\)。
如果 \(cdq\) 分治是搞 \(dp\) 的话,那就是分治完左区间然后对右区间进行转移,最后再分治右区间。
我们先按照原顺序排序处理贡献,最后再按照 \((x,y)\) 进行排序即可。
代码
时间复杂度 \(\mathcal{O}(n\log n)\)。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <stdlib.h>
#include <algorithm>
#include <vector>
#define int long long
#define sqr(x) (x) * (x)
#define N 100005//完了,上瘾了
using namespace std;
int n,h[N],w[N],f[N],sumh[N],sumw[N];
struct node{
int x,y,id;
bool operator<(const node&adv) const {
if (adv.x ^ x) return x < adv.x;
return y < adv.y;
}
}a[N],tmp[N];
double X(int id) {
return h[id];
}
double Y(int id) {
return f[id] + h[id] * h[id] - sumw[id];
}
// double Slope(int fir,int sec) {
// if (X(fir) == X(sec)) {
// return Y(fir) > Y(sec) ? 1e18 : -1e18;
// }
// return (Y(fir) - Y(sec)) / (X(fir) - X(sec));
// }
double Slope(int fir,int sec) {
if (a[fir].x == a[sec].x) return a[fir].y > a[sec].y ? 1e18 : -1e18;
return (1.0 * a[fir].y - a[sec].y) / (1.0 * a[fir].x - a[sec].x);
}
int q[N];
void solve(int l,int r) {
if (l == r) {
a[l].y = Y(l);
return ;
}
int mid = l + r >> 1;
int lt = l - 1,rt = mid;
for (int i = l;i <= r;i ++)
if (a[i].id <= mid) tmp[++lt] = a[i];
else tmp[++rt] = a[i];
for (int i = l;i <= r;i ++) a[i] = tmp[i];
solve(l,mid);
int head = 1,tail = 0;
for (int i = l;i <= mid;i ++) {
if (i > l && a[i].x == a[i - 1].x) continue;
while(head < tail && Slope(q[tail - 1],q[tail]) > Slope(q[tail],i)) tail --;
q[++tail] = i;
}
for (int i = mid + 1;i <= r;i ++) {
while(head < tail && (a[q[head]].y - a[q[head + 1]].y) >= 2 * a[i].x * (a[q[head]].x - a[q[head + 1]].x)) head ++;
int u = a[i].id,v = a[q[head]].id;
f[u] = min(f[u],f[v] + sumw[u - 1] - sumw[v] + sqr(h[u] - h[v]));
}
solve(mid + 1,r);
int i = l,j = mid + 1;
int now = l;
while(i <= mid && j <= r) {
if (a[i].x < a[j].x || (a[i].x == a[j].x && a[i].y < a[j].y)) tmp[now] = a[i],now ++,i ++;
else tmp[now] = a[j],now ++,j ++;
}
while(i <= mid) tmp[now] = a[i],now ++,i ++;
while(j <= r) tmp[now] = a[j],now ++,j ++;
for (int i = l;i <= r;i ++) a[i] = tmp[i];
}
signed main(){
cin >> n;
for (int i = 1;i <= n;i ++) scanf("%lld",&h[i]),sumh[i] = sumh[i - 1] + h[i];
for (int i = 1;i <= n;i ++) scanf("%lld",&w[i]),sumw[i] = sumw[i - 1] + w[i];
for (int i = 1;i <= n;i ++) f[i] = 1e18,a[i].id = i,a[i].x = h[i];
f[1] = 0;
stable_sort(a + 1,a + 1 + n);
solve(1,n);
// for (int i = 1;i <= n;i ++)
// for (int j = 1;j < i;j ++)
// f[i] = min(f[i],f[j] + sqr(h[i] - h[j]) + sumw[i - 1] - sumw[j]);
cout << f[n];
return 0;
}

浙公网安备 33010602011771号