1. 题目来源
链接:3493. 最大的和
相关题目:[字典树] aw3485. 最大异或和(trie+贪心+前缀和+滑动窗口+美团2021)
2. 题目解析
每个数两种状态,可选、不可选。若想将可选状态的和最大,那就得让不可选的和最大,有区间长度限制,就采用滑动窗口维护即可。
两种方法,前缀和或者双指针。前缀和维护不可选状态的数的前缀和,方便进行滑动窗口的区间查找。双指针就直接维护滑动窗口找到窗口内的不可选状态数的最大和即可。本质是一样的,都是找到长度限制下不可选状态的最大和。
注意数据范围 1e5*1e5
会爆 int
,记得开 long long
。
值得注意的是,滑动窗口维护一个 len
长度的窗口,最后的 for
循环 i
的起始下标针对 0、1 有一定的区别:
- 当
i=0
作为循环起始时,窗口滑出判断为i>=m
。 - 当
i=1
作为循环起始时,窗口滑出判断为i>m
。这两点是显然的。 - 滑出元素均为
a[i-m]
、加入元素均为s[i]
。
再有前缀和加入时,比如 [字典树] aw3485. 最大异或和(trie+贪心+前缀和+滑动窗口+美团2021) 本题中,由于求 [L,R]
区间是 S[R] - S[L-1]
,所以它的滑出元素其实是窗口滑出元素的后一个,这样才能利用前缀和求到窗口内的区间和。所以在 [字典树] aw3485. 最大异或和(trie+贪心+前缀和+滑动窗口+美团2021) 就出现了 if (i - m > 0) insert(s[i - m - 1], -1);
这种情况,实际上要知道,它是将滑动窗口滑出元素的后一个进行删除,而不是传统意义上的滑出元素。
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n)
前缀和:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 1e5+5;
int n, k;
LL a[N], b[N], s[N];
int main() {
cin >> n >> k;
for (int i = 1; i <= n; i ++ ) cin >> a[i];
for (int i = 1; i <= n; i ++ ) cin >> b[i];
LL cnt = 0;
for (int i = 1; i <= n; i ++ ) {
if (!b[i]) s[i] = s[i - 1] + a[i];
else s[i] = s[i - 1], cnt += a[i];
}
LL res = 0;
for (int i = 1, j = i + k - 1; j <= n; i ++ ,j ++ )
res = max(res, s[j] - s[i - 1]);
cout << res + cnt << endl;
return 0;
}
双指针:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 1e5+5;
int n, m;
int a[N], b[N];
int main() {
cin >> n >> m;
for (int i = 0; i < n; i ++ ) cin >> a[i];
for (int j = 0; j < n; j ++ ) cin >> b[j];
LL cnt = 0;
for (int i = 0; i < n; i ++ )
if (b[i])
cnt += a[i];
LL res = 0, sum = 0;
for (int i = 0; i < n; i ++ ) { // 双指针维护滑动窗口
if (i - m >= 0 && !b[i - m]) sum -= a[i - m];
if (!b[i]) sum += a[i];
res = max(res, sum);
}
cout << res + cnt << endl;
return 0;
}