AcWing
  • 首页
  • 课程
  • 题库
  • 更多
    • 竞赛
    • 题解
    • 分享
    • 问答
    • 应用
    • 校园
  • 关闭
    历史记录
    清除记录
    猜你想搜
    AcWing热点
  • App
  • 登录/注册

AcWing 302. 任务安排3【二分优化斜率优化DP】    原题链接    困难

作者: 作者的头像   一只野生彩色铅笔 ,  2021-09-26 21:28:39 ,  所有人可见 ,  阅读 2628


37


4

最近在补全提高课所有题目的题解,宣传一下汇总的地方提高课题解补全计划

题目描述

有 $n$ 个 任务 排成一个 序列,顺序不得改变,其中第 $i$ 个 任务 的 耗时 为 $t_i$, 费用系数 为 $c_i$

现需要把该 $n$ 个 任务 分成 若干批 进行加工处理

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

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

分析

为方便读者更好的阅读本题解,建议先看一下我写的上一篇,因为本篇中不会对重复地方进行讲解
AcWing 301. 任务安排2【斜率优化DP模板】

本题 相较于 上一题 的不同之处在于:$-512 \le t_i \le 512$

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

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

$$ \begin{aligned} &f_i = \min\bigg(f_j + S \times (sc_n - sc_j) + st_i \times (sc_i - sc_j)\bigg) \\\\ 提出常量后的剩余部分:&f_j - sc_j \times (S + st_i) \\\\ 换元:&y - kx \\\\ \end{aligned} $$

此处的换元是令 $\begin{cases} f_j = y_j \\\ sc_j = x_j \\\ k_i = S+st_i \end{cases}$

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

且满足 $k_{j-1, j} \le k_i \lt k_{j,j+1}$

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

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

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

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

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

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

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

把点插入 队列 前,先要 队列 中 至少有两个点,然后把 满足 $k_{q_{tt-1}~,~q_{tt}} \ge k_{q_{tt}~,~i}$ 的 点 $q_{tt}$ 弹出

即 新加入的点,必须和 原点集 构成 下凸壳,无效点要先删去

这里我把公式展开,方便大家理解:

$$ k_{q_{tt-1}~,~q_{tt}} \lt k_{q_{tt}~,~i} \Rightarrow \frac{y_{q_{tt}}-y_{tt-1}}{x_{q_{tt}}-x_{tt-1}} \lt \frac{y_{i}-y_{tt}}{x_{q_{i}}-x_{tt}} \Rightarrow \frac{f_{q_{tt}}-f_{tt-1}}{sc_{q_{tt}}-sc_{tt-1}} \lt \frac{f_{i}-f_{tt}}{sc_{q_{i}}-sc_{tt}} $$

这样,队列 中 相邻两点 之间构成的直线 斜率单增,也就是我们的 有效下凸壳点集

总结一下本题要点:

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

Code

时间复杂度:$O(n\log n)$

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 3e5 + 10;

int n;
LL S, sc[N], st[N], f[N], que[N];

int main()
{
    scanf("%d%lld", &n, &S);
    for (int i = 1; i <= n; i ++ )
    {
        scanf("%lld%lld", &st[i], &sc[i]);
        st[i] += st[i - 1], sc[i] += sc[i - 1];
    }
    int tt = 0;
    for (int i = 1; i <= n; i ++ )
    {
        int l = 0, r = tt;
        while (l < r)
        {
            int mid = (l + r) >> 1;
            if (f[que[mid + 1]] - f[que[mid]] > 
               (S + st[i]) * (sc[que[mid + 1]] - sc[que[mid]])) r = mid;
            else l = mid + 1;
        }
        f[i] = f[que[r]] + S * (sc[n] - sc[que[r]]) + st[i] * (sc[i] - sc[que[r]]);
        while (tt && (double)(f[que[tt]] - f[que[tt - 1]]) * (sc[i] - sc[que[tt]]) >= 
                     (double)(f[i] - f[que[tt]]) * (sc[que[tt]] - sc[que[tt - 1]]))
            tt -- ;
        que[ ++ tt] = i;
    }
    printf("%lld\n", f[n]);
    return 0;
}

23 评论


用户头像
XChara   2022-08-08 10:14      1    踩      回复

大佬我有个问题:为什么”while(tt&&……“那里要加double?

用户头像
XChara   2022-08-08 10:15         踩      回复

没有用除法,一直用的乘法

用户头像
XChara   2022-08-08 10:16    回复了 XChara 的评论      1    踩      回复

而且题目说了N,S,T,C都是整数

用户头像
关静小朋友   2022-08-16 23:53         踩      回复

同问,不加就WA,加了就能AC,很纳闷

用户头像
夜未眠   2022-08-17 17:28    回复了 关静小朋友 的评论         踩      回复

难道是long long 越界???

用户头像
关静小朋友   2022-08-17 17:29    回复了 夜未眠 的评论         踩      回复

确实是的,我后来看到y总代码下面评论区有人发了

用户头像
XChara   2022-08-18 21:20    回复了 关静小朋友 的评论         踩      回复

搞明白了,其实最好用__int128,double大了之后会损失精读

用户头像
我也是超级巨星全职高手小蜂鸟飞呀   2025-02-05 05:24    回复了 XChara 的评论         踩      回复

时间花在这种错误上确实没啥意思


用户头像
NoMercyᝰ   2025-03-24 21:13 · 黑龙江         踩      回复

提问:while (tt && (double)(f[que[tt]] - f[que[tt - 1]]) * (sc[i] - sc[que[tt]]) >=
(double)(f[i] - f[que[tt]]) * (sc[que[tt]] - sc[que[tt - 1]])) 中将>= 改成 > 为什么就wa了?


用户头像
kbzcz   2022-10-18 09:55         踩      回复

为什么二分l=mid+1,r=mid;
我用l=mid-1,r=mid+1就WA了

用户头像
征途者   2022-10-31 18:04         踩      回复

那么二分就要 l <= r 了

用户头像
kbzcz   2022-11-20 16:13    回复了 征途者 的评论         踩      回复

我是这么写的,但还是WA了

用户头像
kbzcz   2022-11-20 16:13    回复了 征途者 的评论         踩      回复
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define int long long
const int N=300005;
struct node {
    int t,c;
}a[N];
int f[N];
int st[N],sc[N];
double X(int j) {return 1.0*sc[j];}
double Y(int j) {return 1.0*f[j];}
double slope(int j1,int j2) {return (Y(j2)-Y(j1))/(X(j2)-X(j1));}
int q[N],head,tail;
int B_S(int k) {
    if(head==tail) return q[head];
    int l=head,r=tail,ans;
    while(l<=r) {
        int mid=l+r>>1;
        if(Y(q[mid])-Y(q[mid]-1)<=k*(X(q[mid])-X(q[mid-1]))) ans=mid,l=mid+1;
        else r=mid-1;
    }
    return q[ans];
}
signed main()  {
    int n,S;
    scanf("%lld%lld",&n,&S);
    for(int i=1;i<=n;i++) {
        scanf("%lld%lld",&a[i].t,&a[i].c);
        st[i]=st[i-1]+a[i].t;
        sc[i]=sc[i-1]+a[i].c;
    }
    memset(f,0x3f,sizeof(f));
    f[0]=0;
    head=tail=1;
    q[1]=0;
    for(int i=1;i<=n;i++) {
        int j=B_S(st[i]+S);
        f[i]=f[j]-(S+st[i])*sc[j]+st[i]*sc[i]+sc[n]*S;
        while(head<tail&&(Y(q[tail])-Y(q[tail-1]))*(X(i)-X(q[tail]))>=(Y(i)-Y(q[tail]))*(X(q[tail])-X(q[tail-1]))) tail--;
        q[++tail]=i;
    }
    printf("%lld",f[n]);
} 
用户头像
征途者   2022-11-20 19:28    回复了 kbzcz 的评论         踩      回复

你看你 B_S 函数 Y(q[mid]-1) 这个 -1 跑到外面去了……, 后面的 X[] 又对了……

用户头像
kbzcz   2022-11-29 18:21    回复了 征途者 的评论         踩      回复

哦哦

用户头像
kbzcz   2022-11-29 18:22    回复了 征途者 的评论         踩      回复

wssb


用户头像
蓝色一定是最幸福的颜色   2022-02-02 23:08         踩      回复

大佬我想问一下如果C也不是单调的怎么办啊

用户头像
bruhify   2022-02-11 21:23      1    踩      回复

C也不是单调的可以以斜率为关键字建平衡树维护动态凸壳,寻找决策点的时候在平衡树中查找就行了,复杂度不变

用户头像
懵逼自动机   2023-05-13 07:23    回复了 bruhify 的评论         踩      回复

Orz

用户头像
acwing_xyy   2023-10-06 17:42    回复了 bruhify 的评论         踩      回复

用李超树能更快一些

用户头像
辣鸡号航母   2024-07-30 20:58    回复了 acwing_xyy 的评论         踩      回复

CDQ离线也可以


用户头像
Hygen   2021-09-26 21:42         踩      回复

马上就可以专心备考了!

用户头像
一只野生彩色铅笔   2021-09-26 21:50         踩      回复

是的hh


App 内打开
你确定删除吗?
1024
x

© 2018-2025 AcWing 版权所有  |  京ICP备2021015969号-2
用户协议  |  隐私政策  |  常见问题  |  联系我们
AcWing
请输入登录信息
更多登录方式: 微信图标 qq图标 qq图标
请输入绑定的邮箱地址
请输入注册信息