Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 5673 Accepted Submission(s): 2049
4 6 3 6 -1 2 -6 5 -5 6 4 6 -1 2 -6 5 -5 6 3 -1 2 -6 5 -5 6 6 6 -1 -1 -1 -1 -1 -1
7 1 3 7 1 3 7 6 2 -1 1 1
解题思路:
求长度不超过K的最大子段和。这道题看了很多解题报告,折腾了快两天,终于理解得差不多了。自己后来写了一遍代码,一次AC。
参考:http://www.cnblogs.com/neverforget/archive/2011/10/13/ll.html
维护一个单调递增的队列,队首元素一定是最小的,sum[j]-sum[i],j是一定的,为区间尾,就是找最小的sum[i],而这个sum[i]一定是存在队首的。之前看过一个结题报告说,本题为了维护单调,在插入元素前要删除队尾元素,在被删除的元素其实并不是真正的被删除,还是存在队列中,后来想想,也是这样回事,因为虽然删除了元素,但长度K是一定的,这个长度K是不能根据当前队列里有多少元素来算,而是要根据下标,而这些下标就包括队尾已经被删除的元素(这种情况被插入的元素不是队首),如果把尾节点全部删除了,当前插入的元素为队首,那么那些删除的元素可以当做已经不存在了,区间长度由当前队首元素(下标)来计算。队列里最多只能有K个元素,这里元素都是指的下标。一开始被插入的元素为下标0.
Sum[j]- sum[i] =sum[i+1]+sum[i+2]+…..+sum[j]
比如数据
Num: 2 -1 8 -5 K=3
Sum : 2 1 9 4 sum[0]=0
下标 1 2 3 4
I=1 插入 0
I=2 插入 1 单调递增 sum[0]<sum[1]
I=3 sum[2]<sum[1],删除1 ,插入2 ,这时候可以算出前三个数的和,这也是第一个长度为K 的序列和。
I=4 因为坐标限制0<4-K,删除首元素,插入3,这时候队列里有2 3,想想这时候为什么这时候要删除1那个元素,sum[4]-sum[1]= 2 sum[4]-sum[2]= 3 ,后面那个是最优的,所以这也是优先队列首元素为最小值的用处所在。队列里面不可能出现4,因为自己减自己没意义。
代码:
#include <iostream> #include <queue> #include <stdio.h> #include <string.h> using namespace std; const int maxn=100005; const int inf=0x7fffffff; int num[maxn]; int sum[maxn*2]; int n,k; int main() { int t;scanf("%d",&t); while(t--) { scanf("%d%d",&n,&k); sum[0]=0; for(int i=1;i<=n;i++) { scanf("%d",&num[i]); sum[i]=sum[i-1]+num[i]; } for(int i=1;i<k;i++) sum[n+i]=sum[n+i-1]+num[i]; int temp=n; n=n+k-1; deque<int>q; int ans=-inf; int start,end; for(int i=1;i<=n;i++) { while(!q.empty()&&sum[i-1]<sum[q.back()])//维护队列的单调递增,首元素为最小值 q.pop_back(); while(!q.empty()&&q.front()<i-k) q.pop_front();//区间长度为K这个限制 q.push_back(i-1); if(sum[i]-sum[q.front()]>ans) { ans=sum[i]-sum[q.front()];//ans为num[q.front()+1]+num[q.front()+2]+....num[i]的和 start=q.front()+1; end=i; } } printf("%d %d %d\n",ans,start,(end>temp?end%temp:end)); } }