循环数组最大子段和(带限制的最大子段和,单调队列优化)

题目链接:here

 

 

 

 

 

 
题解:对于不带限制的最大字段和我们可以:求一遍前缀和,求出最大值最小值,最后结果 res = max( MAX,  SUM - MIN );
那么对于这道题相当于带了限制:限制最大子段的长度是len:我们可以维护一个前缀和 ,然后结果就是 max( sum[i] - min(sum[j]) ), i-len <= j <= i-1 && 1<=i <= 2*n,注意这里j>=i-len,而不是i-len+1,因为前缀和相减的时候要注意会把j位置上的那个数也减去)。然后这么看来 是n^2的dp,那么如何优化呢。
可以预处理  长度为len的区间的最小值  然后对于每个i 查询前面区间长度为 len 的最小值 然后 sum[i] - sum[j]即可
我们可以维护一个单调队列,单调队列要满足两点:
  • 队内元素的位置 要符合 \(i\)的区间要求 即 i-len <=que[j]<=i-1
  • 单调性,队列内的元素 尽可能小(因为我们要减去最小值呀),维护一个单调非递减队列(递增或持平)

AC_Code:

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 typedef long long ll;
 4 const int maxn = 1e6+10;
 5 const ll inf = 0x3f3f3f3f3f3f3f3f;
 6 deque<ll>q;
 7 ll a[maxn];
 8 ll n,m;
 9 ll sum[maxn];
10 
11 int main()
12 {
13     scanf("%lld",&n);
14     for(ll i=1;i<=n;i++){
15         scanf("%lld",&a[i]);
16         sum[i] = sum[i-1]+a[i];
17     }
18     for(ll i=n+1;i<=2*n;i++){
19         a[i] = a[i-n];
20         sum[i] = sum[i-1]+a[i];
21     }
22     m = n;
23     n<<=1;
24 
25     while( !q.empty() ) q.pop_front();
26     ll res = 0;
27     q.push_back(0);
28     for(ll i=1;i<=n;i++){
29         while( !q.empty() && q.front()<i-m ) q.pop_front();
30         res = max(res,sum[i]-sum[q.front()]);
31         while( !q.empty() && sum[q.back()]>=sum[i] ) q.pop_back();
32         q.push_back(i);
33     }
34     printf("%lld\n",res);
35     return 0;
36 }

 

参考博客:here

 

posted @ 2020-09-16 20:05  swsyya  阅读(190)  评论(0编辑  收藏  举报

回到顶部