尼克的任务(线性dp):正序遍历与逆向遍历的区别的好题

99 篇文章 0 订阅

尼克的任务

题目描述

尼克每天上班之前都连接上英特网,接收他的上司发来的邮件,这些邮件包含了尼克主管的部门当天要完成的全部任务,每个任务由一个开始时刻与一个持续时间构成。

尼克的一个工作日为 n n n 分钟,从第 1 1 1 分钟开始到第 n n n 分钟结束。当尼克到达单位后他就开始干活,公司一共有 k k k 个任务需要完成。如果在同一时刻有多个任务需要完成,尼克可以任选其中的一个来做,而其余的则由他的同事完成,反之如果只有一个任务,则该任务必需由尼克去完成,假如某些任务开始时刻尼克正在工作,则这些任务也由尼克的同事完成。如果某任务于第 p p p 分钟开始,持续时间为 t t t 分钟,则该任务将在第 ( p + t − 1 ) (p+t-1) (p+t1) 分钟结束。

写一个程序计算尼克应该如何选取任务,才能获得最大的空暇时间。

输入格式

输入数据第一行含两个用空格隔开的整数 n n n k k k

接下来共有 k k k 行,每一行有两个用空格隔开的整数 p p p t t t,表示该任务从第 p p p 分钟开始,持续时间为 t t t 分钟。

输出格式

输出文件仅一行,包含一个整数,表示尼克可能获得的最大空暇时间。

样例 #1

样例输入 #1

15 6
1 2
1 6
4 11
8 5
8 1
11 5

样例输出 #1

4

提示

数据规模与约定
  • 对于 100 % 100\% 100% 的数据,保证 1 ≤ n ≤ 1 0 4 , 1 ≤ k ≤ 1 0 4 , 1 ≤ p ≤ n , 1 ≤ p + t − 1 ≤ n 1 \leq n \leq 10^4,1 \leq k \leq 10^4,1 \leq p \leq n,1 \leq p+t-1 \leq n 1n104,1k104,1pn,1p+t1n

思路

  • 首先,本题的实质是区间内放不相交的线段,使空余部分尽可能少。并且能放的线段必须放。

  • 其次,我们先看数据范围,这道题最多只能支持 O ( n 2 ) O(n^2) O(n2) 的时间复杂度,我一开始想到的是贪心,但后面想着想着好像能找到反例(就是你正序的来,局部依次选长度最短的任务(也就是持续时间相较于同一时间来说),就是这样会错,为什么呢?因为有的区间可能比较长,但它结束比较早,也有的区间虽然短,但结束时间晚),而且贪心只能求放线段的数量(如 凌乱的yyy/线段覆盖 ),无法求长短。

  • 那么贪心失效,我们只能用dp来做了,状态表示: f i f_i fi 表示所有以 i i i结尾(这边是从末尾开始)的空闲长度最长的集合。

  • 状态转移:当本时刻无任务, f i = f i + 1 + 1 f_i=f_{i+1}+1 fi=fi+1+1

  • 当本时刻有任务, f i = m a x ( f i , f i + w s u m ) f_i=max(f_i,f_{i+w_{sum}}) fi=max(fi,fi+wsum),其中 w s u m w_{sum} wsum 指的是这个时刻的任务持续的时间,为什么是加上呢?因为区间是往右的。这个的目的也就是找出选择哪一个本时刻任务使空闲时间最大化。

  • 这边还有个细节就是为什么逆序遍历,参考省流:讲的就是如果是顺序遍历的话,其价值不一定为真实可达的价值,因此初始化负无穷判断能否为真。逆序遍历的话,对于目前状态的价值一定为真,其余状态的真假会在以后判断,因此无需初始化。

AC 代码

//f[i]表示1~i的最大空闲时间

#include<iostream>
#include<algorithm>

#define int long long

using namespace std;

const int N = 1e4+10;

struct E{
    int l,sum;
}e[N];
int n,k;
int f[N];
int s[N];
int cnt=1;

signed main(){
    cin>>n>>k;
    
    for(int i=1;i<=k;i++){
        int a,b;
        cin>>a>>b;
        e[i]={a,b};
        s[a]++;//同一时间多个任务选简单的
    }
    
    sort(e+1,e+1+k,[&](E a,E b){
        return a.l>b.l;
    });
    
    for(int i=n;i>=1;i--){
        //没有任务的时候
        if(!s[i]){
            f[i]=f[i+1]+1;
        }else{
            //有任务
            for(int j=1;j<=s[i];j++){
                f[i]=max(f[i],f[i+e[cnt].sum]);
                cnt++;
            }
        }
    }
    
    cout<<f[1]<<endl;
    
    return 0;
    
}
  • 21
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

green qwq

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值