持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第29天,点击查看活动详情。
题目信息描述
自习室内有一个智能灯。
在 00 时刻,管理员会将打开电闸,并将灯点亮。
在 MM 时刻,管理员会直接拉下电闸,此时,如果灯处于点亮状态,则会因为断电而熄灭。
在 0∼M0∼M 之间有 nn 个不同时刻,不妨用 a1,a2,…,ana1,a2,…,an 表示,其中 0<a1<a2<…<an<M0<a1<a2<…<an<M。
在这 nn 个时刻中的每个时刻,管理员都会拨动一次智能灯的开关,使灯的状态切换(亮变灭、灭变亮)。
现在,你可以最多额外指定一个时刻(也可以不指定),让管理员在此时刻也拨动开关一次。注意选定的时刻不能与 a1,a2,…,ana1,a2,…,an 相等。
你的目的是让亮灯的总时长尽可能长。
输出这个最大亮灯总时长。
输入格式
第一行包含整数 TT,表示共有 TT 组测试数据。
每组数据,第一行包含两个整数 nn 和 MM。
第二行包含 nn 个整数 a1,a2,…,ana1,a2,…,an。
输出格式
输出一个整数,表示最大亮灯总时长。
数据范围
1≤T≤301≤T≤30, 1≤n≤1051≤n≤105, 2≤M≤1092≤M≤109, 0<a1<a2<…<an<M0<a1<a2<…<an<M。 同一测试点内所有 nn 的和不超过 105105。
输入样例:
3
3 10
4 6 7
2 12
1 10
2 7
3 4
输出样例:
8
9
6
思路
将给定的时刻作为区间右端点,注意要添加最终的 M 时刻,当时刻的下标为偶数时表示该区间最开始是灯亮的状态,当时刻的下标是奇数时表示该区间最开始时灯灭的状态。
第一次遍历时统计最开始灯亮和灯灭的时间之和,则灯亮时间之和是不插入新时刻的答案。
第二次遍历时枚举每个区间,维护已经遍历过的区间的灯亮时间之和和灯灭时间之和,当区间长度 < 2 时无法插入新时刻所以不考虑,其他情况下考虑在该区间插入新时刻的最大值。
当该区间最开始是灯亮状态,则在区间右端点左侧插入新时刻,从而保证该区间在插入新时刻后灯亮状态时间最长; 当该区间最开始是灯灭状态,则在区间左端点右侧插入新时刻,从而保证该区间在插入新时刻后灯亮状态时间最长。
对于插入新时刻之后区间,灯的状态会发生反转,所以加上插入时刻后面灯灭时间之和就是在该区间插入新时刻可以得到的最大灯亮时间之和。
代码
# include <bits/stdc++.h>
using namespace std;
int main()
{
int T; cin >> T;
while (T --)
{
int n; cin >> n;
int M; cin >> M;
int a[n + 2];
int an = n + 2;
a[0] = 0;
for (int i = 1; i < n + 1; i ++)
cin >> a[i];
a[n + 1] = M;
int b[an - 1];
for (int i = 1; i < an; i ++)
b[i-1] = a[i] - a[i-1];
int bn = an - 1;
int presum_even[bn + 1]; //前缀和
memset(presum_even, 0, sizeof(presum_even));
int presum_odd[bn + 1];
memset(presum_odd, 0, sizeof(presum_odd));
for (int i = 0; i < bn; i ++)
{
int zone = b[i];
presum_even[i+1] = presum_even[i];
presum_odd[i+1] = presum_odd[i];
if (i % 2 == 0)
{
presum_even[i+1] += zone; //奇偶性从0开始计
}
else
{
presum_odd[i+1] += zone;
}
}
//---- 假设不动
int res = presum_even[bn];
//---- 动了
for (int i = 0; i < bn; i ++)
{
int zone = b[i];
if (zone == 1)
continue;
int L = presum_even[i];
int mid = zone - 1;
int R = presum_odd[bn] - presum_odd[i+1];
res = max(res, L + mid + R);
}
cout << res << endl;
}
return 0;
}