Acwing 3774.亮灯时长

该博客讨论了如何在给定的一系列开关操作和限制条件下,通过额外的操作来最大化自习室智能灯的亮灯总时长。作者提出了一种策略,涉及到前缀和或后缀和的概念,用于计算不同区间内的亮灯和不亮灯时间,并通过调整操作位置来找到最佳方案。博客提供了输入输出样例和相应的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述:

自习室内有一个智能灯。

在 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

思路:

设a0 = 0,an+1 = m,在[a0,an+1]区间内,0时刻灯被点亮,所以亮灯时间为[a0,a1],[a2,a3],[a4,a5]……等奇数区间,而对应的不亮灯的区间为[a1,a2],[a3,a4],[a5,a6]……等偶数区间。

我们假设在[ai,ai+1]区间内加入一次操作,则[a0,ai]区间内的亮灯时间不受影响,而[ai+1,an+1]区间内的亮灯时间为加入操作前的不亮灯的时间,状态翻转。所以我们需要记录原来的亮灯时间light[]和不亮灯时间dark[],这里可以用到前缀和。[a0,ai]区间的亮灯时间为light[i]-light[0],[ai+1,an+1]区间的亮灯时间对应原来的不亮灯时间dark[n+1]-dark[i+1]。

for(int i=1;i<=n;i++){
    light[i] += light[i-1];
    dark[i] += dark[i-1];
    if(i % 2 == 1) light[i] += a[i] - a[i-1];   //亮灯时间为原来的奇数区间
    else dark[i] += a[i] - a[i-1];
}

也可以使用后缀和来求亮灯时间和不亮灯时间。(注意边界n的值)

for(int i=n-1;i>=0;i--){    //后缀和
    light[i] = light[i+1];
    dark[i] = dark[i+1];
    if(i % 2 == 0) light[i] += a[i+1] - a[i];   //下标值要注意判断
    else dark[i] += a[i+1] - a[i];
} 

而在[ai,ai+1]加操作的区间内,亮灯时间为t-1,也就是a[i+1]-a[i]-1,若[ai,ai+1]区间大小为1,无法加入更多的操作,需要跳出判断。

另外还要注意,若不加操作,亮灯时长就是原先的总亮灯时间light[n]。

代码:

利用前缀和计算

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
using namespace std;
const int N = 1e5+5;
int a[N];
int light[N],dark[N];
int main()
{
    ios::sync_with_stdio(false);
    int t;
    cin>>t;
    while(t--){
        int n,m;
        cin>>n>>m;
        memset(light,0,sizeof(light));
        memset(dark,0,sizeof(dark));
        light[n] = dark[n] = 0;     
        for(int i=1;i<=n;i++) cin>>a[i];
        a[0] = 0,a[++n] = m;    //n值+1
        for(int i=1;i<=n;i++){
            light[i] += light[i-1];
            dark[i] += dark[i-1];
            if(i % 2 == 1) light[i] += a[i] - a[i-1];
            else dark[i] += a[i] - a[i-1];
        } 
        int res = light[n];
        for(int i=0;i<n;i++){
            int cnt = a[i+1]-a[i];
            if(cnt == 1) continue;
            res = max(res,light[i] - light[0] + dark[n] - dark[i+1] + cnt-1);
        }
        cout<<res<<'\n';
    }
    return 0;
}

利用后缀和计算

#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
const int N = 1e5+5;
int a[N];
int light[N],dark[N];
int main()
{
    ios::sync_with_stdio(false);
    int t;
    cin>>t;
    while(t--){
        int n,m;
        cin>>n>>m;
        for(int i=1;i<=n;i++) cin>>a[i];
        a[++n] = m; //n值+1
        light[n] = dark[n] = 0;
        for(int i=n-1;i>=0;i--){    //后缀和
            light[i] = light[i+1];
            dark[i] = dark[i+1];
            if(i % 2 == 0) light[i] += a[i+1] - a[i];
            else dark[i] += a[i+1] - a[i];
        } 
        int res = light[0];
        for(int i=0;i<n;i++){
            int cnt = a[i+1]-a[i];
            if(cnt == 1) continue;
            res = max(res,light[0] - light[i] + dark[i+1] + cnt-1);
        }
        cout<<res<<'\n';
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值