单调队列+dp POI 2015&QBXT Test Ⅴ T2 WIL-Wilcze doły

17 篇文章 0 订阅
2 篇文章 0 订阅

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

首先选长度为 d d d的区间一定优于选长度小于 d d d的区间,而这种固定区间的大概需要 O ( n ) O(n) O(n)复杂度的 d p dp dp问题,很容易想到用单调队列优化。

首先先是朴素的 d p dp dp,我们用双指针, i i i表示选择的区间的右端点, l a s t last last表示以 i i i为右端点的区间保证数字之和 ≤ p ≤p p的最靠左的那个数的位置,随着 i i i的增大, l a s t last last一定单调不降。

但是现在可以将一段区间变为 0 0 0,所以单调队列维护的就是 l a s t last last i i i这个区间内所有长度为 d d d的连续区间的和的最大值的区间右端点。首先求一个前缀和,并用 b b b数组记录序列中所有长度为 d d d的区间的和。

d + 1 d+1 d+1个位置开始枚举,每次将 i i i放入队列中,踢掉在 i i i之前且比 i i i小的,如果队首元素的左端点比 l a s t last last还小,则将队首元素剔除。对于 l a s t last last指针的更新我们需要判断 l a s t last last i i i这段区间的和减去这个区间的最大值是否大于 p p p,对于每一次 l a s t last last指针的更新,都要看队首元素的左端点和 l a s t last last的大小关系,踢掉无用的队头元素。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int n,dp[2001000],d,a[2001000],ans,q[2001000];
long long p,sum[2001000],b[2001000];
int main()
{
	scanf("%d%lld%d",&n,&p,&d);
	for(int i=1;i<=n;++i)
	{
		scanf("%d",&a[i]);	
		sum[i]=sum[i-1]+a[i];
	}	
	for(int i=d;i<=n;++i)
		b[i]=sum[i]-sum[i-d];
	ans=d;
	int head=1,tail=0,last=1;
	for(int i=d+1;i<=n;++i)
	{
		while(head<=tail&&b[i]>b[q[tail]])
			tail--;	
		q[++tail]=i;
		while(head<=tail&&q[head]-d+1<last)
			head++;//如果队首元素的左端点比last还小
        while(head<=tail&&sum[i]-sum[last-1]-b[q[head]]>p) 
        {
            last++;
            while(head<=tail&&q[head]-d+1<last)
				head++;
        }
		ans=max(ans,i-last+1);	
	}
	cout<<ans;
	return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值