JOYOI 最大子序和(单调队列)
输入一个长度为n的整数序列,从中找出一段不超过M的连续子序列,使得整个序列的和最大。
例如 1,-3,5,1,-2,3
当m=4时,S=5+1-2+3=7
当m=2或m=3时,S=5+1=6
本题是一个单调队列的问题,准确来说是前缀和+单调队列。
为啥要单调队列呢?我们来康康这个问题的意思:从一个序列里找一个子序列的和使得它是所有子序列和里边最大的那个。既然提到了区间和,那就用前缀和来简化计算。所以这个问题自然而然的就变成了找sum[x]-sum[y]最大了。枚举x,找一个最小的sum[y]作差,就是本题的策略。如何找到最小的sum[y]呢?就用单增的单调队列进行操作。
模拟一下样例,就是如下的一系列操作。
- 对所有元素作前缀和
- 把0,也就是sum(0)塞入队列中(这是给sum(1)用的,因为可能sum(1)-sum(0)就是最大的子序列,不加这个0就没法计算了),最小值为0
- sum(1)=1,大于队尾元素0,队内最小值为0,入队列
- sum(2)=-2,小于队内所有元素,驱逐1,0后,-2入队列,队内最小值为-2
- sum(3)=3,大于队尾元素-2,入队列,队内最小值为-2
- sum(4)=4,大于队尾元素-2,入队列,此时0即将在i-M(4-4)范围之外了,将0移出队列,队内最小值为-2
- sum(5)=2,小于队内元素3,4,驱逐3和4,2入队列,此时1在范围之外,将1移除。队内最小值为-2
- sum(6)=5, 大于队尾元素2,入队列,队内最小值为-2,计算完最大值之后,将-2移除。
import java.io.*;
import java.util.*;
import java.math.*;
import java.text.*;
public class Main37
{
public static class Data
{
int value,index;
public Data(int a,int b)
{
this.value=a;
this.index=b;
}
}
public static void main(String args[])throws IOException
{
StreamTokenizer st=new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
PrintWriter pw=new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
st.nextToken();
int n=(int)st.nval;
st.nextToken();
int interval=(int)st.nval;
int arr[]=new int[n+50];
Data datas[]=new Data[n+50];
Deque<Data> q=new ArrayDeque<Data>();
for(int i=1;i<=n;i++)
{
st.nextToken();
arr[i]=(int)st.nval;
}
datas[0]=new Data(0,0);
q.add(datas[0]);
for(int i=1;i<=n;i++)
datas[i]=new Data(datas[i-1].value+arr[i],i);
int ans=0;
for(int i=1;i<=n;i++)
{
while(q.size()>0&&q.getLast().value>=datas[i].value)
q.removeLast();
q.addLast(datas[i]);
ans=Math.max(ans,datas[i].value-q.getFirst().value);
//pw.println(datas[i].value+" "+q.getFirst().value);
if(q.getFirst().index==i-interval)
q.removeFirst();
}
pw.println(ans);
pw.flush();
pw.close();
}
}