[CEOI2017]Building Bridges 题解

传送门

s i s_i w i w_i 的前缀和, f i f_i 表示将第 1 1 根柱子与第 i i 根柱子连接的最小代价。考虑最后一座桥从第 j j 根柱子架到第 i i 根柱子,于是枚举 j j ,有转移

f i = min { f j + s i 1 s j + ( h i h j ) 2 } f_i=\min\{f_j+s_{i-1}-s_j+(h_i-h_j)^2\}

把平方展开,提出仅与 i i 有关的项

f i = min { f j s j + h j 2 2 h i h j } + s i 1 + h i 2 f_i=\min\{f_j-s_j+h_j^2-2h_ih_j\}+s_{i-1}+h_i^2

y i = f i s i + h i 2 , x i = h i , k i = 2 h i y_i=f_i-s_i+h_i^2,x_i=h_i,k_i=2h_i ,那么

f i = min { y j k i x j } + s i 1 + h i 2 f_i=\min\{y_j-k_ix_j\}+s_{i-1}+h_i^2

是比较显然的斜率优化。由于这里斜率和横坐标都不单调,不能简单用单调队列解决。可以使用splay维护动态凸包。

这里斜率是正的,要最小化截距,我们需要维护下凸壳。

在splay上维护目前的下凸壳上的结点,结点横坐标从小到大的顺序即为splay中序遍历的顺序。

在splay上对每个节点 x x 记录 l k x \mathrm{lk}_x r k x \mathrm{rk}_x 两个值,分别代表在凸壳上 x x 左侧那个点与它的连线的斜率(不妨称为“左线斜率”)和它右侧那个点与它连线的斜率(不妨称为“右线斜率”)。

我们可以在splay上二分来找到对于 i i 的最优决策点 j j 进行转移。对于当前节点 x x

  • 如果 l k x k i r k x \mathrm{lk}_x\leq k_i\leq\mathrm{rk}_x ,说明这条直线在这里与凸壳相切,这个点 x x 就是最优决策点。

  • 如果 k i < l k x k_i<\mathrm{lk}_x ,说明切点在左侧,对左子树递归调用。

  • 如果 k i > r k x k_i>\mathrm{rk}_x ,说明切点在右侧,对右子树递归调用。

求出 f i f_i 之后要将这个点插入平衡树。首先按横坐标递增顺序找到它的插入位置,插入这个结点并splay到根。然后检查它左右的结点,把由于它的插入而不满足凸包性质的点删除,同时求出它的左右线斜率。最后检查如果它的左线斜率大于右线斜率,说明这个结点不在凸壳上,将它删除。

两个注意点: f 1 f_1 f 2 f_2 要单独拎出来写,还有注意斜率不存在的情况。

类似的题目可以看看NOI2007的货币兑换

#include <cctype>
#include <cstdio>
#include <climits>
#include <algorithm>
#include <cmath>

template <typename T> inline void read(T& x) {
    int f = 0, c = getchar(); x = 0;
    while (!isdigit(c)) f |= c == '-', c = getchar();
    while (isdigit(c)) x = x * 10 + c - 48, c = getchar();
    if (f) x = -x;
}
template <typename T, typename... Args>
inline void read(T& x, Args&... args) {
    read(x); read(args...); 
}
template <typename T> void write(T x) {
    if (x < 0) x = -x, putchar('-');
    if (x > 9) write(x / 10);
    putchar(x % 10 + 48);
}
template <typename T> inline void writeln(T x) { write(x); puts(""); }
template <typename T> inline bool chkmin(T& x, const T& y) { return y < x ? (x = y, true) : false; }
template <typename T> inline bool chkmax(T& x, const T& y) { return x < y ? (x = y, true) : false; }

typedef long long LL;

const int maxn = 1e5 + 207;
const double inf = 1e16, eps = 1e-10;

LL s[maxn], h[maxn], f[maxn];
double lk[maxn], rk[maxn], xc[maxn], yc[maxn];
int ch[maxn][2], fa[maxn], root;
int n;

inline double slope(int i, int j) {
    if (fabs(xc[i] - xc[j]) <= eps) return yc[i] < yc[j] ? inf : -inf;
    return (yc[i] - yc[j]) / (xc[i] - xc[j]);
}
inline int iden(int x) {
    return ch[fa[x]][0] == x ? 0 : (ch[fa[x]][1] == x ? 1 : -1);
}
inline void rotate(int x) {
    int d = iden(x), y = fa[x];
    if (~iden(y)) ch[fa[y]][iden(y)] = x;
    fa[x] = fa[y];
    if ((ch[y][d] = ch[x][d ^ 1])) fa[ch[x][d ^ 1]] = y;
    fa[ch[x][d ^ 1] = y] = x;
}
inline void splay(int x, int &k) {
    if (x == k) return;
    for (int p = fa[k], y = fa[x]; y != p; y = fa[x]) {
        if (fa[y] != p) rotate(iden(y) ^ iden(x) ? x : y);
        rotate(x);
    }
    k = x;
}
int queryBest(int x, double k) {
    if (!x) return 0;
    if (lk[x] <= k + eps && k <= rk[x] + eps) return x;
    if (k + eps >= rk[x]) return queryBest(ch[x][1], k);
    else return queryBest(ch[x][0], k);
}
inline int getPrev(int x) {
    int ans = 0;
    for (int y = ch[x][0]; y;) {
        if (lk[y] <= slope(y, x) + eps) ans = y, y = ch[y][1];
        else y = ch[y][0];
    }
    return ans;
}
inline int getNext(int x) {
    int ans = 0;
    for (int y = ch[x][1]; y;) {
        if (slope(x, y) <= rk[y] + eps) ans = y, y = ch[y][0];
        else y = ch[y][1];
    }
    return ans;
}
inline void update(int &rt, int x) {
    splay(x, rt);
    if (ch[x][0]) {
        int y = getPrev(x);
        splay(y, ch[x][0]);
        ch[y][1] = 0;
        rk[y] = lk[x] = slope(y, x);
    } else lk[x] = -inf;
    if (ch[x][1]) {
        int y = getNext(x);
        splay(y, ch[x][1]);
        ch[y][0] = 0;
        rk[x] = lk[y] = slope(x, y);
    } else rk[x] = inf;
    if (lk[x] + eps >= rk[x]) {
        rt = ch[x][0];
        fa[ch[rt][1] = ch[x][1]] = rt;
        fa[rt] = 0;
        rk[rt] = lk[ch[rt][1]] = slope(rt, ch[rt][1]);
    }
}
void insert(int &x, int dad, int id) {
    if (!x) { fa[x = id] = dad; return; }
    insert(xc[x] <= xc[id] + eps ? ch[x][1] : ch[x][0], x, id);
}

int main() {
    read(n);
    for (int i = 1; i <= n; ++i) read(h[i]);
    for (int i = 1, x; i <= n; ++i) read(x), s[i] = s[i - 1] + x;

    f[1] = 0;
    xc[1] = h[1];
    yc[1] = f[1] - s[1] + 1.0 * h[1] * h[1];
    f[2] = 1ll * (h[2] - h[1]) * (h[2] - h[1]);
    xc[2] = h[2];
    yc[2] = f[2] - s[2] + 1.0 * h[2] * h[2];
    insert(root, 0, 1); update(root, 1);
    insert(root, 0, 2); update(root, 2);

    for (int i = 3; i <= n; ++i) {
        int j = queryBest(root, 2 * h[i]);
        f[i] = f[j] + s[i - 1] - s[j] + (h[i] - h[j]) * (h[i] - h[j]);
        xc[i] = h[i];
        yc[i] = f[i] - s[i] + 1.0 * h[i] * h[i];
        insert(root, 0, i);
        update(root, i);
    }
    writeln(f[n]);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_39677783/article/details/89792595