首页 > 代码库 > 4385: [POI2015]Wilcze do?y

4385: [POI2015]Wilcze do?y


原题:http://www.lydsy.com/JudgeOnline/problem.php?id=4385

Description

给定一个长度为n的序列,你有一次机会选中一段连续的长度不超过d的区间,将里面所有数字全部修改为0。
请找到最长的一段连续区间,使得该区间内所有数字之和不超过p。

Input

第一行包含三个整数n,p,d(1<=d<=n<=2000000,0<=p<=10^16)。
第二行包含n个正整数,依次表示序列中每个数w[i](1<=w[i]<=10^9)。

Output

包含一行一个正整数,即修改后能找到的最长的符合条件的区间的长度。

Sample Input

9 7 2
3 4 1 9 4 1 7 1 3

Sample Output

5

HINT

将第4个和第5个数修改为0,然后可以选出区间[2,6],总和为4+1+0+0+1=6。

ps:很抱歉,尽力地去写,可自己去看的时候还是发现对单调队列的讲解非常模糊,更别说别人了,所以如果要看的话,后半部分关于单调队列优化的地方还是建议去找专门讲解的博客去看,希望有所帮助。

题解:我们换个角度来考虑,当前有个区间i~j,我们如何判断他删去n个数后能达到小于p?当然是删掉这段区间内,相加最大的连续的n个数,这样区间总和减去这相加最大的n个数得到的一定是最小值,我们只需要判断它是否会小于p即可判断这段区间是否会满足条件。如果区间符合条件,那么就一步步的扩大j,使它不符合条件为止,如果不符合条件了,就增加i使它符合条件为止,在每次的增加过程中,我们都可以通过现在这个区间的长度去更新答案。

那么如何去找现在这个区间内,和最大,连续的n个数呢?这里就要用到单调队列了,设f[k]表示第k个数到第k+n-1个数的和,每次扩大j,就肯定会有一个新的f[j-n+1]出现,我们用一个队列p来储存f[k],性质为队列中的f[k]一定在区间内,且队列的队首永远是现在这个区间内最大的f[k],队内元素从大到小排列。我们如何实现?有两步:

   1.移动j,每次产生一个新的f[k]我们就把他压入队列p的队尾,如果它要大于它前面的那个数就取代他的位置,不停比较,直到它小于前面的那个数为止。

   2.若在区间i的移动中,我们发现队首突然不在队列中了,那么就去掉队首。

为什么这么做?首先我们会发现如果枚举去找区间i~j和区间i+1~j+1的中的最大f[k]时有很多的元素是重复比较的,不同的只是区间i+1~j中少了一个f[i],多了一个f[j-n+2]而已,而我们上面的第一步就是把这个f[j-n+2]去和以前的f[k]作比较,如果它要大于之前的f[k]的话,之前的f[k]就再也不可能是之后区间中的最大f[k]了,既然没有利用价值,留你何用?所以我们果断把它代替掉,第二部,因为当前最大的这个f[k],已经不在i+1~j+1的区间中了,所以当然也把他删掉,找下一个次大的f[k]。

这么做后我们会发现,每次队列中的第一个数一定是包含在区间内的最大f[k],我们找的时间就变为了O(1)。

好啦具体程序注释:

 

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
long long n,p,d,qhead,ans,head,t,num,x[2000005],s[2000005],f[2000005];
int main()
{
    cin>>n>>p>>d;
    for (int i=1;i<=n;i++) 
    {
      cin>>num;    
      s[i]+=s[i-1]+num;
    }
    for (int i=1;i<=n-d+1;i++) x[i]=s[i+d-1]-s[i-1];//储存连续的n个数的和
    for (int i=n-d+2;i<=n;i++) x[i]=s[n]-s[i-1];
    qhead=0;//队列开头
    head=1;//区间开头
    t=1;
    for (int i=d+1;i<=n;i++)
    {
       while ((x[i-d]>x[f[t]])&&(t>=qhead)) t-=1;//往队尾加上这次新加上的 n个数的和,并进行更新
       t+=1;//队列尾
       f[t]=i-d;
       while ((s[i]-s[head]-x[f[qhead]]>p)&&(head<i-d))//看看当前区间是否符合条件,如果不符合的话改变区间
       {
             head+=1;
             if (f[qhead]<head) qhead+=1;//如果队首的那个最大值已经不在区间内了,就删去。
       }
       ans=max(ans,i-head);//更新答案
    }
    cout<<ans<<endl;
    return 0;
}

 

4385: [POI2015]Wilcze do?y