• AT2347 [ARC070C] NarrowRectangles


    首先不难看出一个暴力的 (dp) 解法,考虑令 (dp_{i, j}) 表示考虑完前 (i) 个矩形,第 (i) 个矩形左端点在 (j) 时所需要的最小花费。

    不难有转移:

    [dp_{i, j} = minlimits_{j - (r_{i - 1} - l_{i - 1} ) le k le j + (r_{i} - l_{i})}{dp_{i - 1, k}} + |j - l_i| ]

    继续考虑对上面这个 (dp) 进行优化,可以发现的是,这个 (dp) 本质上是在对之前的某个区域取 (min) 然后再加上一个之和每个位置有关的绝对值函数。

    看起来十分抽象,那么我们可以考虑把抽象问题具体化,将 (dp_i) 看作是一个关于 (j) 的函数,再来考虑一下转移。

    从初始的 (i = 1) 开始考虑,不难发现 (dp_1) 就是 (y = |j - l_1|) 这样一个绝对值函数。

    再来考虑 (i = 2) 的情况,可以发现前一步取 (min) 本质上是将这个绝对值函数的左段向左平移了 (r_i - l_i),右段向右平移了 (r_{i - 1} - l_{i - 1}),中间用一段斜率为 (0) 的线段接起来。

    再来考虑加入 (|j - l_i|) 这个绝对值函数,不难发现图像的变化需要分三种情况,但整体的变化方式都是确定的,即 :

    • (l_i) 左边的函数斜率整体 (-1) 右边的函数斜率整体 (+1),同时斜率变化的点的横坐标不变。

    可以发现,这样变化后的图像一定还是一个下凸包。

    那么就不难通过归纳证明每次转移的过程都是类似于 (i = 2) 的情况的。

    同时,因为每一个函数 (dp_i) 的斜率最大最小值都不会超过 (pm |2i - 1|),因此我们可以考虑直接维护这些斜率变化的拐点。

    为了方便起见,如果在这个拐点斜率变化了 (k),我们就直接插 (k) 个这个点进去代表斜率变化了 (k)(1)

    首先,因为左右两个函数的平移是不同的,因此我们要分开维护左右两边的断点。

    并且,因为函数只是平移,因此我们只需要记录当前平移了多长的距离即可,下面着重来考虑加入一个绝对值函数的过程。

    • 当插入的绝对值函数零点在图像斜率为 (0) 的那段上时。

    不难发现左边之前的拐点还是拐点,右边亦然。唯一变化的是左边右都会要在 (l_i) 处添加一个拐点。

    • 当插入的绝对值函数零点在图像斜率为 (0) 的那段左边时。

    不难发现此时左边会在 (l_i) 处添加两个拐点,同时失去最靠右的拐点,右边会得到左边失去的哪个拐点。(注意这里的拐点实际上代表着斜率变化为 (1),这样就方便地处理掉左边做靠右的直线斜率不为 (-1) 的情况了)

    因为需要取出最右边的端点,因此我们需要一个大根堆来维护左边的拐点。

    • 当插入的绝对值函数零点在图像斜率为 (0) 的那段右边时。

    同理于上一种情况,用小根堆维护右边的拐点。

    那么最终的答案是什么呢?

    不难发现其实是最终 (dp_n) 斜率为 (0) 的那段函数对应的纵坐标。

    但是我们并没有记录每一次的纵坐标,怎么办呢?

    你会发现每次斜率为 (0) 的直线位置总是会向右或者向左变化,而这两次斜率为 (0) 的直线中会存在一个交点。

    同时因为对于每一层而言,取斜率为 (0) 线段上的点总是最优的。

    结合上面那条性质,每次选定一个斜率为 (0) 的线段做为这一层的 (j) 即可,然后计算答案只要计算往下一层斜率为 (0) 交点纵坐标的变化量即可。

    #include <bits/stdc++.h>
    using namespace std;
    #define int long long
    #define rep(i, l, r) for (int i = l; i <= r; ++i)
    const int N = 1e5 + 5;
    int n, l, r, nL, nR, TL, TR, len, ans, lastlen;
    priority_queue <int> L;
    priority_queue <int, vector <int>, greater <int> > R;
    int read() {
        char c; int x = 0, f = 1;
        c = getchar();
        while (c > '9' || c < '0') { if(c == '-') f = -1; c = getchar();}
        while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
        return x * f;
    }
    signed main() {
        n = read(), l = read(), r = read();
        L.push(l), R.push(l), lastlen = r - l;
        for (int i = 2; i <= n; ++i, lastlen = len) {
            l = read(), r = read(), len = r - l, TL += len, TR += lastlen;
            nL = L.top() - TL, nR = R.top() + TR;
            if(l < nL) ans += nL - l, L.pop(), R.push(nL - TR), L.push(l + TL), L.push(l + TL);
            else if(l > nR) ans += l - nR, R.pop(), L.push(nR + TL), R.push(l - TR), R.push(l - TR);
            else L.push(l + TL), R.push(l - TR);
        }
        printf("%lld", ans);
        return 0;
    }
    

    这种方法被称为 ( m set) 维护函数拐点,经常用与维护一次分段函数的变化问题。

    GO!
  • 相关阅读:
    Jquery基础知识与使用
    JavaScript人机交互
    CSS和JS基础
    Html基础
    Struts2第一天:Struts2的概述、Struts2的入门、Struts2常见的配置、Struts2的Action的编写
    MyEclipse中设置类的注释(修改时间,作者等)
    Failed to initialize end point associated with ProtocolHandler ["http-bio-80"] java.net.BindExce问题解决
    Hibernate第四天:Hibernate的查询方式、抓取策略
    Hibernate第三天:Hibernate的一对多配置、Hibernate的多对多的配置
    Neural Network(神经网络)
  • 原文地址:https://www.cnblogs.com/Go7338395/p/13851643.html
Copyright © 2020-2023  润新知