HDU-3415 Max Sum of Max-K-sub-sequence
题目大意
给你一个环形数组 a a a,求长度不超过 k k k的连续非空子段和的最大值以及这个子段的开始位置和结束位置。如果有多个结果,则输出起始位置最小的答案。如果还有多个结果,则输出长度最小的答案。
有 t t t组数据。
1 ≤ t ≤ 100 , 1 ≤ n ≤ 100000 , 1 ≤ k ≤ n , − 1000 ≤ a i ≤ 1000 1\leq t\leq 100,1\leq n\leq 100000,1\leq k\leq n,-1000\leq a_i\leq 1000 1≤t≤100,1≤n≤100000,1≤k≤n,−1000≤ai≤1000
题解
首先,把数组 a a a复制一份,放在 a a a后面,破环为链。
然后,我们求 a [ i ] a[i] a[i]的前缀和 s u m [ i ] sum[i] sum[i]。遍历一遍数组,当前遍历到的位置 i i i视为子段的结束位置。设以 i i i为结束位置的能使子段和最大的开始位置为 j + 1 j+1 j+1,因为子段和为 s u m i − s u m j sum_i-sum_j sumi−sumj,所以只要知道在 [ i − k , i − 1 ] [i-k,i-1] [i−k,i−1]上最小的 s u m j sum_j sumj,即可得到 j j j,这样就能得到开始位置 j + 1 j+1 j+1。用单调队列来维护区间最小前缀和,即可 O ( n ) O(n) O(n)处理每一次询问。
总时间复杂度为 O ( ∑ n ) O(\sum n) O(∑n)。
code
#include<iostream>
#include<cstdio>
using namespace std;
int t,n,k,hd,tl,ans,p1,p2,a[200005],sum[200005],q[200005];
int main()
{scanf("%d",&t);while(t--){scanf("%d%d",&n,&k);for(int i=1;i<=n;i++){scanf("%d",&a[i]);a[n+i]=a[i];}for(int i=1;i<=2*n;i++){sum[i]=sum[i-1]+a[i];}hd=1;tl=1;q[1]=0;ans=-1e9;p1=p2=0;for(int i=1;i<=2*n;i++){while(q[hd]<i-k) ++hd;if(sum[i]-sum[q[hd]]>ans){ans=sum[i]-sum[q[hd]];p1=q[hd]+1;p2=i;}while(hd<=tl&&sum[q[tl]]>=sum[i]) --tl;q[++tl]=i;}if(p2>n) p2-=n;printf("%d %d %d\n",ans,p1,p2);}return 0;
}