P3775数列操作
时间限制 : - MS 空间限制 : 165536 KB
评测说明 : 1000ms
问题描述
给定一个长度为n的序列,你有一次机会选中一段连续的长度不超过d的区间,将里面所有数字全部修改为0。
请找到最长的一段连续区间,使得该区间内所有数字之和不超过p。
输入格式
第一行包含三个整数n,p,d(1<=d<=n<=300000,0<=p<=10^16)。
第二行包含n个正整数,依次表示序列中每个数w[i](1<=w[i]<=10^9)。
输出格式
包含一行一个正整数,即修改后能找到的最长的符合条件的区间的长度。
样例输入
9 7 2
3 4 1 9 4 1 7 1 3
样例输出
5
提示
将第4个和第5个数修改为0,然后可以选出区间[2,6],总和为4+1+0+0+1=6。
来源 poi 2015 Wilcze doły
比较容易想到的一个处理是 对每个点都求出以它为起点(或者是终点)的 长度不大于d的区间和
按贪心来讲 除非是无法使长度达到d 否则是一定要删掉长度为d的区间的
那么我们的问题就在于 如何能够在一段区间内快速寻找到长度为d的和最大的子区间
暂时抛去这些不看 我们先看一些基础操作
首先肯定是预处理出前缀和sum
那么对于每个点p为终点的长度不大于d的子区间
在1<=p<=d时 子区间和为 sum[p]
而当d+1<=p<=n时 子区间和则为 sum[p]-sum[p-d]
于是对于一段区间[l,r] 假如最大长度为d的子区间和为 delta
那么我们需要判断的即是 sum[r]-sum[l-1]-delta 是否 <=p
对于这道题目来讲 时间复杂度O(n^2)是不允许的
我们所能够接受的最大时间复杂度为O(nlog n)(原题的元素个数为200 0000)
但是就目前来看 我们必须讨论以每个点为终点的情况才能够得到结果
这就要求我们的查询的次数一定要小于logn
所以其实你也可以用线段树什么的(理论上可行)
具体的做法是
把每个点为起点的d长度区间之和作为每个点的权值建树
然后查询一段区间的最大子区间和就是常规的线段树操作
(没有尝试过 只是一个想法而已)
可以用二分法求左端点l 然后每次查询线段树[l,r](r为当前讨论的终点)中的最大值
通过上面的判断条件即可判断能否成立
大概的时间复杂度是(log n*log n)【二分法*线段树】
但是我们有一个更简单的工具 单调队列
单调递减队列 存入长度为d的区间的值
对于每个点p为终点的讨论 我们都在队列中加入 sum[p]-sum[p-d] 使得讨论一定有解(标记位置应为p-d+1)
具体操作(请自行配合样例画图进行理解)
假设我们现在已经得到可以成立的区间[l,r](即[l-1,r]不能使条件成立)
那么在我们讨论以r为右端点的成立的区间时 [l-1,r+1]一定是不能成立的
([l-1,r]都不能成立了 [l-1,r+1]还要多一个数字 就更加不可能成立了)
注意此时的右端点全部是 r+1
所以我们在讨论r+1为右端点时 左端点直接从l开始枚举
而且对于单调队列中的元素 左端点也一定要>=r+1
由于单调队列为单调递减队列 所以第一个元素一定是当前区间中 长度为d的子区间的和的最大值
所以这样就能够轻易地得到上面我们想要的结果
并且由于左端点一定在区间之中 且左端点一定<=(r+1)-d+1 (因为我们在单调队列中加入的最后一个元素的左端点为 (r+1)-d+1)
所以删去的区间并不会越界
所以此时我们只需要判断 sum[r+1]-sum[l-1]-队列第一个元素 是否<=p 即可判定成立与否
若成立 那么就可以直接更新结果 且以r+1为右端点的讨论终止(思考原因)
若不成立 那么l+1 同时对当前单调队列的元素的左端点进行判定(左端点>=l+1)
然后重复上述过程
这个样子 其实以左端点也只是把每一个点都讨论了一次
看似时将每一个点作为右端点的情况都讨论了很久
但是其实讨论的总的时间复杂度不过是O(n)
要用long long !!(这真的是终点)
#include
#include
using namespace std;
inline long long input()
{
char c=getchar();long long o;
while(c>57||c<48)c=getchar();
for(o=0;c>47&&c<58;c=getchar())o=(o<<1)+(o<<3)+c-48;
return o;
}
long long que[300123],pos[300123],sum[300123],add;
int res,nl=1,head=1,tail=0,l;
int main()
{
long long n=input(),p=input(),d=input();
res=d;
for(int i=1;i<=n;i++)sum[i]=sum[i-1]+input();
for(int i=d+1;i<=n;i++)
{
add=sum[i]-sum[i-d];
while(head<=tail&&add>=que[tail])tail--;
que[++tail]=add;pos[tail]=i-d+1;
while(sum[i]-sum[nl-1]>p+que[head])
{
nl++;
while(pos[head]1);
}
printf("%d",res);
}