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;
}
时间: 2025-01-06 12:54:43

4385: [POI2015]Wilcze do?y的相关文章

bzoj 4385: [POI2015]Wilcze do?y

4385: [POI2015]Wilcze do?y 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 包含一行一个正整数,即修改后能

BZOJ4385 : [POI2015]Wilcze do?y

求出前缀和$s$,设$f[i]=s[i+d-1]-s[i-1]$. 从左到右枚举的右端点$i$,左端点$j$满足单调性,若$s[i]-s[j-1]-\max(区间内最大的f)\leq p$,则可行. 用单调队列维护即可,时间复杂度$O(n)$. #include<cstdio> #define N 2000010 int n,d,i,j,q[N],h,t,ans;long long p,sum,s[N],f[N]; inline void read(int&a){char c;whil

【BZOJ4385】[POI2015]Wilcze do?y【单调队列】【前缀和】【Two Pointers】

[题目链接] 题解: 如果区间[j, i]固定,那么一定是将权值最大的一段变为0. 用单调队列维护一段区间内权值最大的子段下标(这里记录右端点下标,设为x),枚举右端点i,用尺取法计算出j. 一段区间[j, i]合法的条件是sum[i] - sum[j - 1] - (sum[x] - sum[x - d]) <= p. 复杂度: 时间复杂度:因为每个点最多遍历2次,复杂度为O(n). 空间复杂度:O(n) RE: 更新j的时候忘写h <= t了.如果写对拍的话应该可以拍出来. GET: 如果

@bzoj - [email&#160;protected] [POI2015] Wilcze do?y

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 给定一个长度为 n 的序列,你有一次机会选中一段连续的长度不超过 d 的区间,将里面所有数字全部修改为 0. 请找到最长的一段连续区间,使得该区间内所有数字之和不超过 p . input 第一行包含三个整数 n, p, d (1 <= d <= n <= 2000000,0 &

BZOJ 4385 洛谷3594 POI2015 WIL-Wilcze do?y

[题解] 手残写错调了好久QAQ...... 洛谷的数据似乎比较水.. n个正整数!!这很重要 这道题是个类似two pointer的思想,外加一个单调队列维护当前区间内长度为d的子序列中元素之和的最大值. 枚举右端点,如果左端点到右端点的元素和减去区间内长为d的子序列中元素和的最大值,大于给定的P,那么就把左端点向右挪. #include<cstdio> #include<algorithm> #define N 2000010 #define rg register #defi

bzoj4385 &amp; POJ2015 Wilcze do?y

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 In

bzoj4385 Wilcze do?y

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 包含一行一个正整数,即修改后能找到的最长的符合条件的区间的长度. 单调队列扫描,记录

P3594 [POI2015]WIL-Wilcze do?y

———————————————————————————————————————————————————— 修改一次的单调队列,借助单调队列求出区间内删除达到的最大值,再借助连续增长的l,r求解,挺好的题 —————————————————————————————————————————————————— #include<bits/stdc++.h> using namespace std; long long a[2000005],n,p,d,sum[2000005],q[2000005],

POI2015 解题报告

未完, 待更新... Luogu3585 [POI2015]PIE Solution 模拟, 按顺序搜索, 把搜索到的需要印却没有印的点 和 印章的第一个点重合, 并印上. 另外, 纸上需要印的点 和 印章上沾墨水的点用数组储存, 能加快很多 Code 1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #include<vector> 5 #define R register 6 u