贪心算法②--使用贪心算法思想解会场租赁问题-python

题目描述

Siruseri 政府建造了一座新的会议中心。许多公司对租借会议中心的会堂很 感兴趣,他们希望能够在里面举行会议。
对于一个客户而言,仅当在开会时能够独自占用整个会堂,他才会租借会堂。
会议中心的销售主管认为:最好的策略应该是将会堂租借给尽可能多的客户。显然,有可能存在不止一种满足要求的策略。
例如下面的例子。总共有 4 个公司。他们对租借会堂发出了请求,并提出了他们所需占用会堂的起止日期(如下表所示)。

公司名称  开始日期  结束日期
 公司1    4        9
 公司2    9        11
 公司3    13       19
 公司4    10       17

上例中,最多将会堂租借给两家公司。
租借策略分别是租给公司 1 和公司 3, 或是公司 2 和公司 3,也可以是公司 1 和公司 4。
注意会议中心一天最多租借给 一个公司,所以公司 1 和公司 2 不能同时租借会议中心,因为他们在第九天重合了。
销售主管为了公平起见,决定按照如下的程序来确定选择何种租借策略:
首先,将租借给客户数量最多的策略作为候选,将所有的公司按照他们发出请求的顺序编号。
对于候选策略,将策略中的每家公司的编号按升序排列。最后,选出 其中字典序最小1的候选策略作为最终的策略。
例中,会堂最终将被租借给公司1和公司3:
3个候选策略是 {(1,3),(2,3),(1,4)}。
而在字典序中(1,3)<(1,4)<(2,3)。
你的任务是帮助销售主管确定应该将会堂租借给哪些公司。

输入格式

输入的第一行有一个整数 N,表示发出租借会堂申请的公司的个数。
第 2 到 第 N+1 行每行有 2 个整数。第 i+1 行的整数表示第 i 家公司申请租借的起始和终 止日期。
对于每个公司的申请,起始日期为不小于 1 的整数,终止日期为不大于 10^9 的整数。

输出格式

输出的第一行应有一个整数 M,表示最多可以租借给多少家公司。
第二行应列出 M 个数,表示最终将会堂租借给哪些公司。

样例输入

4
4 9
9 11
13 19
10 17

样例输出

2
1 3

提示
对于 50%的输入,N≤3000。
在所有输入中,N≤200000。

题解思路:

本题满足贪心算法思路,由于是字典序最小,所以按顺序强制使用每个公司,如果使用后还可以达到最优解,就使用。如果选了这个不影响数量,那么选了这个字典序肯定比不选小。

关键是如何快速地得出选了这个公司之后最多能选的公司:
(下列内容将公司抽象成了线段)
首先,思考一下,发现这是个贪心题。让选取的右端点(租赁结束时间最小)尽量小一定是最优的。那么您可以按左区间排序,每次选右端点最小的就行了。然而,它是 O(n) 的复杂度,您需要思考优化。

事实上,这种贪心策略对于不同的区间都是固定的,您可以用 ST 表(倍增)优化它。

设 f i,j为第 i 个点开始选 2j条线段后最小的右端点。

那么有转移方程:

f[i][j] = min { f[i][j] ,f[ f[i][j-1]][j-1] }

然而,这个转移方程没有初值,所以需要赋一个初值。对于每条线段,它的左端点都直接可以通过它到右端点。

在这里插入图片描述
这样就可以通过倍增 O(logn)求出一个区间最多可以放几条线段了。

def query(f, l, r):
    res = 0
    for i in range(l, r):
        if f[i][l] <= r + 1:
            res += 1
            l = f[i][l]
    return res

但是,就这样,我们还是无法判断一条线段的区间有没有被其他线段覆盖。使用线段树/树状数组区间赋值+区间修改?但是这样显然很麻烦,有没有更好的方法呢?
有。我们可以使用珂朵莉树,将区间转换为值,保存 l,r 放进 set 里。修改时找到块删除+插入新的散块就行了。
l,r <= 109
记得跑离散化。

Python精简解:

# 处理输入
def myInput():
    meetings = []
    n = int(input())
    for i in range(n):
        mStr = input().split(' ')
        mInt = []
        for j in mStr:
            mInt.append(int(j))
        meetings.append(tuple(mInt))
    return meetings


# 贪心算法
def eventArrangement(meetings):
    n = len(meetings)
    # 按终止日期升序
    meetings.sort(key=lambda x: x[1])
    # print(meetings)
    # 标志位
    meetingsFlag = [False for i in range(n)]
    j = 0
    meetingsFlag[j] = True
    for i in range(1, n):
        if meetings[i][0] >= meetings[j][1]:
            j = i
            meetingsFlag[j] = True
    return meetingsFlag


# 输出
def output():
    meetings = myInput()
    # print(meetings)
    result = eventArrangement(meetings)
    # print(result)
    changeCount = 0
    for i in result:
        if i:
            changeCount += 1
    return changeCount


counts = output()
print(counts)

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

灰灰老师

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

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

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

打赏作者

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

抵扣说明:

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

余额充值