求解一个数字环中的长度不超过k的连续之和,使得和最大,若存在多个,输出起始位置最小的,同时,长度最短的。
一个单调队列的问题,需要对问题有一个很清楚的了解。设nsum[i]表示前i个数字的和,那么很容易,表示出从i+1 开始的到位置j结束的这串数字的和: nsum[j] - nsum[i],由此我们就可以很容易,得到此问题的解,max{ nsum[j] - nsum[i]},其中 j - i <= k -1 ;问题,到这里还是很难解决。在考虑一下,如果枚举j,我们只需要计算出nsum[j] - nsum[i]的最大值,当j给定的时候,问题就变成找出最小的nsum[i]。此时问题就转化为,max{nsum[j] - min{ nsum[i] } }。那么如何高效的求解区间中的最小的nsum[i]?利用单调队列,维护一个单调递增的队列即可。这样我们就可以很容易的找出序列中的最小元素,那么也就很容易的找出对应的答案。至于环的问题,我们只需要在复制一段即可。
程序如下:
/*
ID: csuchenan
PROG: hdoj 3415
LANG: C++
*/
#include<stdio.h>
#include<string.h>
#include<iostream>
using namespace std ;
#define MAXN 200005
struct Node{
int pos ;
int val ;
}node[MAXN] ;
int nval[MAXN] ;
int nsum[MAXN] ;
int n ;
int k ;
void init() ;
void work() ;
int main(int argc , char *argv[]){
int m ;
scanf("%d" , &m) ;
while(m--){
init() ;
work() ;
}
//system("pause") ;
return 0 ;
}
void init(){
scanf("%d %d" , &n , &k) ;
int i ;
memset(nsum , 0 , sizeof(nsum)) ;
memset(nval , 0 , sizeof(nval)) ;
for(i = 1 ; i <= n ; i ++){
scanf("%d" , &nval[i]) ;
nsum[i] = nsum[i-1] + nval[i] ;
}
for(i = n + 1 ; i <= n + k ; i ++){
nsum[i] = nsum[i-1] + nval[i-n] ;
}
return ;
}
void work(){
int front ;
int rear ;
front = 1 ;
rear = 1 ;
node[1].pos = 1 ;
node[1].val = 0 ;
int i ;
int nmax ;
int spos ;
int epos ;
nmax = nsum[1] ;
spos = 1 ;
epos = 1 ;
for(i = 2 ; i <= n + k ; i ++){
//维护一个单调增队列
while(front <= rear && node[rear].val > nsum[i-1]){
rear -- ;
}
rear ++ ;
node[rear].pos = i ;
node[rear].val = nsum[i-1] ;
//删除队列中老元素,即那些位置超过i-k的。
while(node[front].pos <= i - k){
front ++ ;
}
//更新最大值
if(nmax < nsum[i] - node[front].val){
nmax = nsum[i] - node[front].val ;
epos = i ;
spos = node[front].pos ;
}
}
//此处由于是环,求摸
if(epos > n)
epos = epos - n ;
if(spos > n)
spos = spos - n ;
printf("%d %d %d\n" , nmax , spos , epos) ;
return ;
}