Max Sum of Max-K-sub-sequence HDU - 3415(最大连续k子段和,单调队列)

Max Sum of Max-K-sub-sequence

HDU - 3415
题意:找出循环数列中长度不超过k的最大的子段和;
要找的子段的长度不超过k,普通思路就是由长度为1找到长度为k;O(nk)的复杂度,k小点还好说, 如果k很大就不可行了;
先想到了前缀和,sum[i]-min(sum[i-k~i-1])就是以i结尾的长度不超过k的最大的子段和;
那么是不是可以维护一个队列使得队首时sum[i-k~i-1]最小的?可以单调队列可以办到; 然后队列中的元素一定不超过k个;
有什么用呢?
既然队列的队首时min(sum[i-k~i-1])所以sum[i]-队首一定是最大的;而且满足区间长度不超过k;
接下来每次比较维护一个首尾便于输出就OK了;
还有一点队列里放的是下标,方便记录;队列用双向队列,STL里的deque就行,因为如果队列中元素超了k个就要删除队首,双向队列方便操作队首;

敲黑板!!!队列中的元素并不一定是连续的,所以不能靠计算队列中的元素个数来判断区间长度是否超过k,要通过队首的下标判断;

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <deque>
using namespace std;
const int maxn=1e5+10;
const int INF=0x3f3f3f3f;
int n, k;
int ans, sum[maxn*2], a[maxn*2];
deque<int> deq;
int main(){
	int t;
	scanf("%d", &t);
	while(t--){
		scanf("%d%d", &n, &k);
		for(int i=1; i<=n; i++){
			scanf("%d", &a[i]);
			a[i+n]=a[i];
		}
		sum[0]=0;
		for(int i=1; i<n+k; i++){
			sum[i]=sum[i-1]+a[i];
		}
		int ans=-INF, head=0, tail=0;
		deq.clear();
		for(int i=1; i<n+k; i++){
			while(!deq.empty()&&sum[i-1]<sum[deq.back()]) deq.pop_back();
			deq.push_back(i-1);
			while(!deq.empty()&&deq.front()<i-k) deq.pop_front();
			if(sum[i]-sum[deq.front()]>ans){
				ans=sum[i]-sum[deq.front()];
				head=(deq.front()+1);
				tail=i%n;
			}
		}
		printf("%d %d %d\n", ans, head, tail==0?n:tail);
	}
	return 0;
}


猜你喜欢

转载自blog.csdn.net/sirius_han/article/details/80411273