NKOJ-3775 数列操作

来源:互联网 发布:安卓模拟器知乎 编辑:程序博客网 时间:2024/05/20 12:51

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 23 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 <iostream>#include <cstdio>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]<nl)head++;            res=max(res,i-nl+1);    }    printf("%d",res);}