• AcWing 302 任务安排3


    \(AcWing\) \(302\) 任务安排\(3\)

    题目传送门

    一、题目描述

    \(n\)任务 排成一个 序列顺序不得改变,其中第 \(i\)任务耗时\(t_i\)费用系数\(c_i\)

    现需要把该 \(n\)任务 分成 若干批 进行加工处理

    每批次的 段头,需要 额外消耗 \(S\) 的时间启动机器。每一个任务的 完成时间 是所在 批次结束时间

    完成一个任务的 费用 为:从 \(0\) 时刻 到该任务 所在批次结束 的时间 \(t\) 乘以 该任务 费用系数 \(c\)

    二、题目分析

    本题 相较于 上一题 的不同之处在于:\(−512≤t_i≤512\)

    该限制使得 \(t_i\)前缀和 \(st_i\) 不再是 单调递增 的了

    我们再来观察一下上一篇中推导的公式:

    \[\large f_i=min(f_j+S \times (sc_n-sc_j)+st_i \times (sc_i-sc_j)) \]

    提出常量后的剩余部分:\(\large f_j-sc_j \times (S+st_i)\)
    换元:\(\large y−kx\)

    此处的换元是令

    \[\large \left\{\begin{array}{ll} f_j=y_j& \\ sc_j=x_j& \\ k_i=S+st_i \end{array}\right. \]

    上一篇题解 中提到过,点集 上第一个出现在直线 \(y=kx+b\) 上的点是 下凸壳 上的点

    且满足 \(k_{j−1,j}≤k_i<k_{j,j+1}\)

    下凸壳 上的点集,相邻两点 构成的 斜率单调递增

    在上题中,斜率 \(k(k_i=S+st_i)\) 也是 单调递增 的,故可以用 单调队列队头 维护 大于\(k\)最小值

    而本题中,\(k_i\) 不具备 单调性,因此不能再用 单调队列 优化了

    不过, “下凸壳上的点集,相邻两点构成的斜率是单调递增的

    我们可以利用上 单调性,维护一个 下凸壳的点集,则对于 \(k_i\),找到 大于他的最小值 就可以 二分

    通过利用一个 队列(非 滑动窗口,故不考虑队列最大长度),完成对于 下凸壳点集 的维护即可

    关于如何利用 队列 维护 下凸壳的点集,这在上篇题解中的最后有提到,直接 引用原文 了:

    把点插入 队列 前,先要 队列 中 至少有两个点,然后把 满足 \(k_{q_{tt−1}, q_{tt}} ≥k_{q_{tt},i}\) \(q_{tt}\) 弹出
    新加入的点,必须和 原点集 构成 下凸壳,无效点要先删去
    这里我把公式展开,方便大家理解:
    \(\large \displaystyle k_{q_{tt-1},q_{tt}}<k_{q_{tt,i}} \Rightarrow \frac{y_{q_{tt}}-y_{q_{tt-1}}}{x_{q_{tt}}-x_{q_{tt-1}}}<\frac{y_i-y_{q_{tt}}}{x_i-x_{q_{tt}}} \Rightarrow \frac{f_{q_{tt}}-f_{q_{tt-1}}}{sc_{q_{tt}}-sc_{q_{tt-1}}}<\frac{f_i-f_{q_{tt}}}{sc_{q_i}-sc_{q_{tt}}}\)
    这样,队列相邻两点 之间构成的直线 斜率单增,也就是我们的 有效下凸壳点集

    总结一下本题要点:

    1. 用队列维护 下凸壳点集
    2. 二分 找出 点集 中第一个出现在直线上的点

    二、二分模板

    #include <bits/stdc++.h>
    using namespace std;
    int n, m;
    const int N = 110;
    /*
    前提:q是一个有序递增的数组,当容器中的元素按照递增的顺序存储时,
    */
    int q[N] = {1, 2, 3, 3, 3, 4, 5};
    int l, r;
    
    //二分算法之手动版本
    int manual_lower_bound(int l, int r, int x) {
        while (l < r) {
            int mid = (l + r) / 2;
            if (q[mid] >= x)
                r = mid;
            else
                l = mid + 1;
        }
        return l;
    }
    
    int manual_upper_bound(int l, int r, int x) {
        while (l < r) {
            int mid = (l + r) / 2;
            if (q[mid] > x)
                r = mid;
            else
                l = mid + 1;
        }
        return l;
    }
    
    int main() {
        /**********************************************************/
        //方法1:yxc 大法
        //从0~6找出>=3的第一个位置
        l = 0, r = 6;
        int x = 3;
        while (l < r) {
            int mid = (l + r) >> 1;
            if (q[mid] >= x)
                r = mid;
            else
                l = mid + 1;
        }
        printf("%d\n", l);
    
        //从0~6找出<=3的最后一个位置
        l = 0, r = 6;
        x = 3;
        while (l < r) {
            int mid = (l + r + 1) >> 1;
            if (q[mid] <= x)
                l = mid;
            else
                r = mid - 1;
        }
        printf("%d\n", l);
        /*
        方法2:STL lower_bound,upper_bound 大法
        lower_bound函数返回容器中第一个大于等于目标值的位置
        upper_bound函数返回容器中第一个大于目标值的位置
        若容器中的元素都比目标值小则返回最后一个元素的下一个位置
        */
        printf("%lld\n", lower_bound(q, q + 7, x) - q);
        printf("%lld\n", upper_bound(q, q + 7, x) - q - 1);
        /**********************************************************/
        /*
        方法3:手写 lower_bound,upper_bound大法
        */
        printf("%d\n", manual_lower_bound(0, 7, x));
        printf("%d\n", manual_upper_bound(0, 7, x) - 1);
        /**********************************************************/
        return 0;
    }
    

    三、实现代码

    #include <bits/stdc++.h>
    
    using namespace std;
    
    typedef long long ll;
    const int N = 300010;
    int n, s;
    ll st[N], sc[N], f[N];
    int q[N];
    
    int main() {
      cin >> n >> s;
      for (int i = 1; i <= n; i++) cin >> st[i] >> sc[i], st[i] += st[i - 1], sc[i] += sc[i - 1];
      //初始化队列
      int hh = 0, tt = 0; //添加哨兵
    
      for (int i = 1; i <= n; i++) {  //动态规划,从小到大枚举每个i
          int l = hh, r = tt;
          //二分模板lower_bound
          //通过二分,找到第一个斜率大于k=st[i] + S的两个点,起点就是切点
          while (l < r) {
              int mid = (l + r) / 2;
              // check函数
              if (f[q[mid + 1]] - f[q[mid]] > (st[i] + s) * (sc[q[mid + 1]] - sc[q[mid]]))
                  r = mid;
              else
                  l = mid + 1;
          }
    
          int j = q[l]; //切点位置
    
          //动态规划
          f[i] = f[j] - (st[i] + s) * sc[j] + st[i] * sc[i] + s * sc[n];
    
          //出队尾,斜率比自己大的点都要出凸包队列,小心long long的乘法
          while (hh < tt && (__int128)(f[q[tt]] - f[q[tt - 1]]) * (sc[i] - sc[q[tt - 1]]) >= (__int128)(f[i] - f[q[tt - 1]]) * (sc[q[tt]] - sc[q[tt - 1]]))
              tt--;
          q[++tt] = i;
      }
      printf("%lld\n", f[n]);
      return 0;
    }
    
    
  • 相关阅读:
    Python 数字数据类型
    Python 标准数据类型
    Python 变量类型及变量赋值
    Python 基础语法
    ElasticStack系列之五 & 当前的缺陷与不足
    ElasticStack系列之四 & 索引后半段过程
    ElasticStack系列之三 & 索引前半段过程
    ElasticStack系列之二 & ElasticStack整体架构
    ElasticStack系列之一 & ElasticStack基础概念
    c# 获取本机IP
  • 原文地址:https://www.cnblogs.com/littlehb/p/15822333.html
Copyright © 2020-2023  润新知