题目

有N件兼职,这些兼职都是为期一天的兼职。在这N件兼职中,如果选择第i份兼职的话,会在 Ai天后,拿到报酬Bi

你能够在暑假的某一天选择一份兼职。但是同一份兼职不能在暑假的不同的日子里被重复选择

从今天开始到M天以后(这里包含第M+1天),你能够得到的最大的报酬是多少

你可以从今天开始选择做兼职

条件

  • 输入的数都为整数
  • 1 <= N <= 1000000
  • 1 <= M <= 1000000
  • 1 <= Ai <= 1000000
  • 1 <= Bi <= 1000000

输入
要求以下面的方式输入

N M
A1 B1
A2 B2
A3 B3
.
.
.
An-1 Bn-1

输出
输出所能获取的最大报酬

例1

输入

3 4
4 3
4 1
2 2

输出

5

解释
通过一下的方式选择兼职,可以多的最大的报酬
一共有3份兼职,最后一天是今天起第5天

  1. 第1天,选择(4 3),就是4天后(第5天)获得3报酬的兼职
  2. 第2天,选择(2 2),就是2天后,也是明天起第2天,今天起第3天,获得2报酬的兼职
  3. 第3天,没得选了,因为剩下的(4,1)是后天起第4天,今天起第6天,超过了最后一天是今天起第5天

所以总共是3+2=5,5个报酬

例2

输入

5 3
1 2
1 3
1 4
2 1
2 3

输出

10

解释
通过一下的方式选择兼职,可以多的最大的报酬
一共有5份兼职,最后一天是今天起第4天

  1. 第1天,选择(2 3),就是2天后(第3天)获得3报酬的兼职
  2. 第2天,选择(1 3), 就是1天后,也是明天起第2天,今天起第3天,获得3报酬的兼职
  3. 第2天,选择(1 4), 就是1天后,也是后天起第1天,今天起第4天,获得4报酬的兼职

所以总共是3+3+4=10,10个报酬


解题思路

M的含义

  • 从第1天到第M天是可以打工的
  • 从第2天到第M+1天是可以领工资的

比如说M=4
最后能拿工资的是第5天
最后能够打工的是第4天

M.jpg

从后面的天开始选起

  • 天数越往后,离最后一天就越近,剩下的留给等发工资的天数就越少
  • 天数越往前,离最后一天就越远,剩下的留给等发工资的天数就越多

然后我们手上有一堆打工的清单
比如说
M=4
3天后拿5块钱
5天后拿3块钱
2天后拿1块钱
2天后拿3块钱
1天后拿1块钱

那么对于能打工的第4天来说,能选择的只能是一天后拿到1块钱的兼职

limitation.jpg

符合后些天时间限制的兼职,肯定也满足前些天的时间限制
limitation2.jpg

我们尝试在选择每一天的最大的报酬的时候,先把所有满足时间限制的兼职加入到候补中,再取候补中最大的值

这个候补区是所有天都公用的,因为是从后些天开始累加的

步骤

1.从最后一天开始,因为是最后一天,所以只能选一天后给报酬的,然后有一个选项进入候补,弹出唯一的,这个时候候补区就变成了空的,然后把空的候补给上一天
S4.jpg

2.再进入到第三天,有两份兼职满足条件进入候补,选择报酬最大的弹出,这个时候,候补区剩下一份兼职,然后把剩下一份兼职的候补区给上一天
S3.jpg

3.再进入到第二天,有一份新的兼职满足要求,加入候补,然后弹出最大报酬的兼职,然后把候补区给上一天
S2.jpg

4.再进入到第一天,发现没有新的兼职满足要求,但是候补里还有兼职,弹出唯一的一份兼职
S1.jpg

代码

from heapq import heappop, heappush

def calculate(N,M,arr):
    ret = 0

    j = 0

    res = []

    # 1,2,3,4,5
    #         ↑
    # 4,3,2,1

    # M = 4
    # range(1,5) => 1,2,3,4

    for i in range(1,M+1):
        # i = 1,2,3
        # 比如M=4的话,第三天(最多1天),第二天(最多2天),第一天(最多3天)

        # 记下来循环打工信息资源,以j为标记
        # j为资源编号
        # arr[j][0]为该编号资源的天数


        # 候补的资源,满足所有天数,因为是满足后续的天数来算的

        while (j < N) and (arr[j][0] <= i):

                heappush(res, -arr[j][1]) #headpush 会把最小的顶在前面,也就是说把最大的arr[j][1]也即是报酬顶在最前面
                j = j + 1 #j找到的话就往上顶一格,一共这一天,或者前一天寻找的时候以下一个资源天为开始

        if len(res) > 0:
            ret -= heappop(res) #headpop抛出最前面的值,也就是最下的负数,最大的报酬

    return ret


N,M = map(int,input().split())


# 后面的逻辑是基于前面的从小到大的排序的
list = [list(map(int,input().split())) for i in range(N)]

arr = sorted(list,key=lambda x:x[0])

result = calculate(N,M,arr)

print(result)

总结

最总要的是要理解这两点

  1. 从后往前,每走一天,积累候补,然后没走完一天,弹出报酬最大候补
  2. 候补是公用

伟大不DIAO
1 声望1 粉丝

Done is better than perfect