题目

题面

给你一个长度为 n的整数序列{A1,A2,⋯,A**n},要求从中找出一段连续的长度不超过 m的非空子序列,使得这个序列的和最大。

输入格式

第一行为两个整数 n,m;

第二行为 n个用空格分开的整数序列,每个数的绝对值都小于 1000。

对于50% 的数据1≤n,m≤1e4;

对于 100% 的数据,1≤n,m≤2×1e5。

输出格式

仅一个整数,表示连续长度不超过 m的最大非空子序列和。

样例

Input&Output

6 4
1 -3 5 1 -2 3
7

思路

这题看到求区间和,首先会想到前缀和预处理。然后看到是连续的长度不超过m的非空子序列,想到长度为m的滑动窗口的子序列。

现在要让这个和最大,也就是当前所走到的元素的前缀和减去一个所能取到的最小的前缀和。

所以我们先用滑动窗口动态找到区间最小前缀和,然后再拿当前前缀和减去区间最小前缀和即可。

以下两点需要注意:

1、此时的滑动窗口实际上可以取到m+1

2、若当前所找到的滑动窗口最小前缀和就是当前的值,那么说明一直没有更大的前缀和出现,那么答案为pre[i]-pre[i-1],即还原为元素本身。

代码

 1 #include 
2 #include
3 using namespace std;
4 typedef long long ll;
5 const int maxn = 2e5+10;
6
7 long long pre[maxn];
8 int q[maxn*2];
9 int main()
10 {
11 ios_base::sync_with_stdio(false);
12 cin.tie(NULL);cout.tie(NULL);
13 //
14 int n, k;cin >> n >> k;
15 for(int i = 1;i <= n;++i) {int temp;cin >> temp;pre[i] = pre[i-1]+temp;}
16 //for(int i = 1;i <= n;++i) printf("pre = %lld\n", pre[i]);
17 //
18 ll ret = -maxn;
19 int head, tail, cnt;
20 head = 1, tail = 1;
21 q[head] = 0;
22 cnt = 0;
23 for(int i = 1;i <= n;++i)
24 {
25 while(head <= tail && i - q[head] + 1 > k+1) head++;
26 if(pre[q[head]] >= pre[i])
27 {
28 while(head <= tail && pre[q[head]] >= pre[i]) head++;
29 q[--head] = i;
30 }else
31 {
32 while(head < tail && pre[q[tail]] >= pre[i]) tail--;
33 q[++tail] = i;
34 }
35 if(i == q[head]) ret = max(ret, pre[i]-pre[i-1]);
36 else ret = max(ret, pre[i] - pre[q[head]]);
37 }
38 //
39 cout << ret << endl;
40 return 0;
41 }

2022GDUT寒训专题一J题的


扫描二维码,在手机上阅读!