3774 亮灯时长(枚举 + 贪心 + 前缀和)

1. 问题描述:

自习室内有一个智能灯。在 0 时刻,管理员会将打开电闸,并将灯点亮。在 M 时刻,管理员会直接拉下电闸,此时,如果灯处于点亮状态,则会因为断电而熄灭。在 0∼M 之间有 n 个不同时刻,不妨用 a1,a2,…,an 表示,其中 0 < a1 < a2 < … < an < M。在这 n 个时刻中的每个时刻,管理员都会拨动一次智能灯的开关,使灯的状态切换(亮变灭、灭变亮)。现在,你可以最多额外指定一个时刻(也可以不指定),让管理员在此时刻也拨动开关一次。注意选定的时刻不能与 a1,a2,…,an 相等。你的目的是让亮灯的总时长尽可能长。输出这个最大亮灯总时长。

输入格式

第一行包含整数 T,表示共有 T 组测试数据。每组数据,第一行包含两个整数 n 和 M。第二行包含 n 个整数 a1,a2,…,an。

输出格式

输出一个整数,表示最大亮灯总时长。

数据范围

1 ≤ T ≤ 30,
1 ≤ n ≤ 10 ^ 5,
2 ≤ M ≤ 10 ^ 9,
0 < a1 < a2 < … < an < M。
同一测试点内所有 n 的和不超过 10 ^ 5。

输入样例:

3
3 10
4 6 7
2 12
1 10
2 7
3 4

输出样例:

8
9
6
来源:https://www.acwing.com/problem/content/description/3777/

2. 思路分析:

对于这种题目我们可以先画一下图,可以发现相当于是已知一个数轴,数轴上着若干个点,点实际上就是题目中对应的时刻,注意是一盏灯,在时刻0灯是亮的,每到一个时刻ai,灯就是变为相反的状态,下图是n个时刻灯对应的状态,实际上是对应若干个亮与不亮的区间,我们需要在0~M并且不等于a1~an的时刻中插入一个时刻求解插入一个时刻之后得到的亮灯时长。

可以发现我们可以在上图形成的区间中插入对应的时刻,当插入对应的时刻可以发现影响的是当前位置后面区间的总长,当前位置之前的区间总长是不变的,为了方便我们可以将其分为奇数区间和偶数区间,分情况讨论插入的位置,可以发现可以分为插入位置在奇数区间和偶数区间这两种情况,进一步分析可以发现插入位置为奇数区间还是偶数区间的情况都是一样的如下图所示,总长都为:ai前面的奇数区间 + ai+1后面的偶数区间 + ai与ai+1部分的长度,为了让总和最大所以ai与a+1之间的最大长度为ai+1 - ai - 1。所以我们可以枚举插入的位置,计算当前位置插入之后能够得到的总长更新答案即可。对于奇数区间和偶数区间的和可以使用两个前缀和数组或者列表进行维护,在计算前缀和的时候判断是奇数区间还是偶数区间求解即可,为了方便处理a的下标可以从1开始,并且将时刻M也插入到a中后面再枚举插入位置的时候会用到这个位置,可以使用画图使用具体例子来确定下标的位置

3. 代码如下:

前缀和:

class Solution:
    def process(self):
        T = int(input())
        for c in range(T):
            n, m = map(int, input().split())
            a = [0] + list(map(int, input().split()))
            n += 1
            # 注意是append方法添加一个元素
            a.append(m)
            # s1, s2分别为奇数区间和偶数区间的前缀和, 这里维护的是前缀和
            s1, s2 = [0] * (n + 10), [0] * (n + 10)
            for i in range(1, n + 1):
                s1[i] = s1[i - 1]
                s2[i] = s2[i - 1]
                if i % 2:
                    # 奇数区间
                    s1[i] += a[i] - a[i - 1]
                else:
                    # 偶数区间
                    s2[i] += a[i] - a[i - 1]
            # 枚举插入的位置
            res = s1[n]
            for i in range(n):
                t = a[i + 1] - a[i]
                # 当前不能够插入
                if t == 1: continue
                res = max(res, s1[i] + s2[n] - s2[i + 1] + t - 1)
            print(res)


if __name__ == '__main__':
    Solution().process()

后缀和,与前缀和是相对的只是位置不一样而已:

class Solution:
    def process(self):
        T = int(input())
        for c in range(T):
            n, m = map(int, input().split())
            # 前面加上元素0这样下标可以从1开始比较方便
            a = [0] + list(map(int, input().split()))
            # a中元素的长度加1
            n += 1
            # 将时刻m看成是a中的一个时刻也是满足条件的, 所以需要将其加入到a中
            a.append(m)
            # s1, s2表示奇数区间和偶数区间的区间和(后缀和)
            s1, s2 = [0] * (n + 10), [0] * (n + 10)
            for i in range(n - 1, -1, -1):
                s1[i] = s1[i + 1]
                s2[i] = s2[i + 1]
                # 判断时候奇数区间还是偶数区间
                if i % 2: s2[i] += a[i + 1] - a[i] # 偶数区间
                else: s1[i] += a[i + 1] - a[i] # 奇数区间
            # 一开始时候总长为奇数区间的前缀和
            res = s1[0]
            # 枚举当前插入的位置
            for i in range(n):
                t = a[i + 1] - a[i]
                # 当前位置不能够插入
                if t == 1: continue
                res = max(res, s1[0] - s1[i] + s2[i + 1] + t - 1)
            print(res)


if __name__ == '__main__':
    Solution().process()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值