6358. 【NOIP2019模拟2019.9.15】小ω的仙人掌

题目

题目大意

给你一串二元组 ( a i , b i ) (a_i,b_i) (ai,bi)的数列。
求最小的区间 [ l , r ] [l,r] [l,r]长度,满足 [ l , r ] [l,r] [l,r]中的每个二元组选或不选,使得 ∑ a i = w \sum a_i=w ai=w ∑ b i ≤ k \sum b_i\leq k bik


思考历程

想了好久,想来想去都是一个背包……
最终决定打暴力……


正解

先说说GMH大爷的神奇解法。
首先是二分答案 a n s ans ans,转化成判定问题。然后在数列中每 a n s ans ans个点设置一个观测点。
以每个观测点为中心,向左和向右背包,然后合并。

然而正解并不需要一个 log ⁡ \log log
考虑双指针,就是记一个当前的最佳答案 a n s ans ans,后面的区间长度都要小于 a n s ans ans。脑补一下这个过程,其实这就是一个队列,只需要支持左边出右边入的队列。
但是背包问题不满足可减性。于是就有个非常骚的解法:
把这个队列用两个栈来代替,栈顶分别为队头和队尾。
加入的时候,就在第二个栈的栈顶加入;弹出的时候,就直接弹出第一个栈的栈顶。
如果第一个栈为空,那就将第二个栈里的东西倒过来放到第一个栈中,然后暴力重构。
每个元素只会暴力重构一次,所以不会时间超限。


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cassert>
#define N 10010
#define maxW 5010
inline int input(){
	char ch=getchar();
	while (ch<'0' || '9'<ch)
		ch=getchar();
	int x=0;
	do{
		x=x*10+ch-'0';
		ch=getchar();
	}
	while ('0'<=ch && ch<='9');
	return x;
}
int n,W,K,a[N],b[N];
int f[N][maxW];
int st1[N],top1,st2[N],top2;
inline void update(int &a,int b){a>b?a=b:0;}
inline bool ok(int j){
	int jj=st1[top1];
	for (int k=0;k<=W;++k)
		if (f[jj][k]+f[j][W-k]<=K)
			return 1;
	return 0;
}
int main(){
	freopen("cactus.in","r",stdin);
	freopen("cactus.out","w",stdout);
	n=input(),W=input(),K=input();
	for (int i=1;i<=n;++i)
		a[i]=input(),b[i]=input();
	int ans=n+1;
	f[0][0]=0;
	for (int i=1;i<=W;++i)
		f[0][i]=K+1;
	for (int i=1,j=1;i<=n;++i){
		if (ok(st2[top2]))
			ans=j-i;
		for (;j<=n && j-i+1<ans;++j){
			st2[++top2]=j;
			int lst=st2[top2-1];
			memcpy(f[j],f[lst],sizeof(int)*(W+1));
			for (int k=0;k+a[j]<=W;++k)
				update(f[j][k+a[j]],f[lst][k]+b[j]);
			if (ok(j))
				ans=j-i+1;
		}
		if (!top1){
			for (int j=top2;j>=1;--j)
				st1[++top1]=st2[j];
			top2=0;
			for (int j=1;j<top1;++j){
				int now=st1[j],lst=st1[j-1];
				memcpy(f[now],f[lst],sizeof(int)*(W+1));
				for (int k=0;k+a[now]<=W;++k)
					update(f[now][k+a[now]],f[lst][k]+b[now]);
			}
		}
		top1--;
	}
	if (ans==n+1)
		printf("-1\n");
	else
		printf("%d\n",ans);
	return 0;
}

总结

还有这么骚的栈操作……
这告诉我们有时候维护队列的东西可以用两个栈来搞。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值