一、题目
传送门:P1714 切蛋糕 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
二、题解和思路
这题是要我们在长度为n的区间里找到一个长度小于等于m的子区间并使得区间和最大,解决这题我们可以考虑对于给定的数据,让每一个数据去向前遍历求得最大和,然后依次和每一项数据的小于等于前m项求得的最大和去比较,找到这样的前m项最大和。
如下表对案例n = 5,m=2 {1 2 3 4 5}的演示:
起始项 | 已起始项去找到的最大和的过程 | Max |
1 | 1+2 | 3 |
2 | 2+3 | 5 |
3 | 3+4 | 7 |
4 | 4+5 | 9 |
所以最大和为9
如果数据很大,n和m很大时,O(n*m)那便显得有点无力,能否有更简便的方法?
下面我们来了解什么是单调队列:
单调队列,顾名思义,是一种具有单调性的队列。众所周知,单调性有单调递增和单调递减两种,相应的单调队列也分为单调递增队列和单调递减队列两种。
-
单调递增队列:保证队列头元素一定是当前队列的最小值,用于维护区间的最小值。
-
单调递减队列:保证队列头元素一定是当前队列的最大值,用于维护区间的最大值。
实现流程:
实现单调队列,主要分为三个部分:
- 去尾操作 :队尾元素出队列。当队列有新元素待入队,需要从队尾开始,删除影响队列单调性的元素,维护队列的单调性。(删除一个队尾元素后,就重新判断新的队尾元素)
去尾操作结束后,将该新元素入队列。
-
删头操作 :队头元素出队列。判断队头元素是否在待求解的区间之内,如果不在,就将其删除。(这个很好理解呀,因为单调队列的队头元素就是待求解区间的极值)
-
取解操作 :经过上面两个操作,取出 队列的头元素 ,就是 当前区间的极值 。
我们以案例为例: n=6,m=3 {1 -2 3 -4 5 -6}
它的前缀和为1 -1 2 -2 3 -3
从前缀和我们可以看出数据的单调性,因为对于每一项前缀和来说,它都是对于前n项的和。也就意味这,它的和变小了,它就递减了。同理,它的和变大了,那它就递增了。
这个时候我们引入了一个单调队列。
队首维护一个最小的和。
我们可以逆向的去想,把要求以这项数据为起点去找最大变成以这项数据为终点,去找到达这项数据的前m和最大是多少?对于每一个到达该数据的区间的区间和都有sum[i]-sum[i-x](其中x∈【1,k】,其中因为是对于第i项数据来说,求得最大区间和,sum[i]是固定的但因为x在变。说以只要保证sum[i-x]最小,那区间和不是最大的吗?
我们画图理解对于案例的前缀和创建i 和 sum[i]的图标,去探索理解这个问题的本质。
如我们要求到达3的前m项和最大那我们只要用sum[3](2)减去最小的在区间内的-1(i=2时的sum[2])
得到结果3
同理要求到达5的前m项和最大那我们只要用sum[5](3)减去最小的在区间内的-2 (i=4时的sum[4])得到结果5
这样我们就可以一次找到区间并迅速对比找到
要注意的是,由于可能出现初始入队元素全为递增的情况,导致不能算到第一个可能使得和最大的数,所以我们要在队头加一个0,以便计算到第一个元素的值。
如这样一组数据 5 6 7 8 9 -2 -2 -2 -2 ....求 m=5时的最大,当-2要入队前,队列中为 5 6 7 8 9,如果没有在对队头加0,这时的和为 (6+7+8+9)也就是30,不能算到5,因为计算和时是5是为队头的开区间。如果有0,当-2要入队时,此时队列就变成了 0 5 6 7 8 9 最大和就变成了35,这时的正确答案。
接下来我们用表格演示一下实现原理帮组大家理解:
1. 案例一 :1 2 3 4 5 n = 5 , m =2
前缀和为:1 3 6 10 15
现队列情况 | 队列操作 | 要求的Max |
0- | 初始化队头加个0 | 0 |
0 1 | 1>0入队 | 1 |
0 1 3 | 3>1入队 | 3-0=3 |
1 3 6 | 6>3入队,但队列长度大于3 ,0出队 | 6-1=5 |
3 6 10 | 同上............ | 10-3=7 |
6 10 15 | ............. | 15-6=9 |
所以答案为9
2. 案例二 1 -2 3 -4 5 -6 n =6 ,m = 3
前缀和为:1 -1 2 -2 3 -3
现队列情况 | 队列操作 | 要求的Max |
0 | 初始化队头加个0 | 0 |
0 1 | 1>0,1出队 | 1-0=1 |
-1 | -1<1,1出队 | 1 |
-1 2 | 2>--1,2入队 | 2-(-1)=3 |
-1 -2 | -2<2,2出队,-2入队 | max(-2 - (-1) ,3)=3 |
-2 3 | 3>-2,3入队,-1超出区间范围出队 | max(3-(-2),3)=5 |
-3 | --3<3,-3<-2 , -2,3出队,-3进队 | 5 |
答案为 5
可以看出队列是单调递增的,计算和时,对头并不记入范围。队列中的长度为m+1,可以直观的找到区间最大和,我们可以直观的看出为什么队列初始时头部为0的作用。
三、代码区
#include<iostream>
using namespace std;
int x,sum[500005],que[500005],m,n,front,rear,Max,S;
int main(){
cin>>n>>m;
for(int i =1;i<=n;i++){
cin>>x;
sum[i]=sum[i-1]+x;//前缀和
}
front=rear=1;//先在队列中添加个sum[0]=0
for(int i=1;i<=n;i++){
while(front<=rear&&que[front]<i-m)front++;//保证队列在区间内
S=sum[i]-sum[que[front]]; //求得区间和
Max=max(Max,S);
while(front<=rear&&sum[que[rear]]>=sum[i])rear--; //保证队列的单调性
que[++rear]=i;
}
cout<<Max;
return 0;
}
附上结果: