把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

斜率优化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\) 次,那么有:

\[f_{i,j}=\max_{l\in[0,i-1]} f_{l,j-1}+sum_l\times(sum_i-sum_l) \]

顺序无关,所以我先在 \(i\) 切,然后再切前面的。

考虑斜率优化,对于 \(j<l\)\(j\)\(l\) 更优,并设 \(f_{x,j-1}=f_x\)

有:

\[f_j+sum_isum_j-sum_j^2>f_l+sum_isum_l-sum_l^2 \Rightarrow \frac{(f_j-sum_j^2)-(sum_l-sum_l^2)}{-sum_j-(-sum_l)}>sum_i \]

那么点集就是 \((-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\)

显然有:

\[f_i=\min_{j\in[1,i-1]}f_j+(h_i-h_j)^2-sumw_{i-1}-sumw_j \]

那么我们有 \(\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\) 菜。

有:

\[f_j+h_j^2-2h_ihj-sumw_j>f_l+h_l^2-2h_ih_l-sumw_l \]

移项得到:

\[(f_j+h_j^2-sumw_j)-(f_l+h_l^2-sumw_l)>2h_i(h_j-h_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;
}
posted @ 2025-10-05 10:44  high_skyy  阅读(7)  评论(0)    收藏  举报
浏览器标题切换
浏览器标题切换end