Luogu P9588 队列 题解
考虑转化问题,将原问题转化为一个长度为 \(q\) 的序列。序列中 \(x\) 表示一段 \(1\sim x\) 的区间。
操作 \(1\)
每次增加时,输入 \(x\),在数组末尾添加一个元素 \(x\),并记录时间 \(t\) 和前缀和 \(s\)。时间表示这个元素在第 \(t\) 个增加操作增加。由于操作 \(4\) 需要求最大值,考虑维护一个单调队列,并执行一次单调队列队尾入队操作,权值为 \(x\),时间为 \(t\)。
操作 \(2\)
维护一个 \(now\) 表示现在已经删除了多少元素,维护一个 \(l\) 表示删除之后序列剩余的元素所处的区间的时间。输入 \(x\),令 \(now\) 增加 \(x\),并逐步向后比较 \(now\) 与前缀和 \(s\) 的值。若大于,说明这一段已经被删除,右移 \(l\)。
操作 \(3\)
考虑在原序列上二分。由于有删除,所以查询当前的 \(x\) 元素实际上就是查询历史第 \(x+now\) 个元素。二分,找到第一个大于 \(x+now\) 的前缀和 \(s\),则这个值必然落在这个区间中。利用 \(s\) 与 \(x+now\) 的差值求出所查询的元素离段尾的距离,然后由段尾元素推回来即可。
操作 \(4\)
考虑单调队列。所有时间小于 \(l\) 的元素都需要出队,因为它们已经被删除。然后取队首,就是最大值。
时间复杂度 \(O(n\log n)\),瓶颈是操作 \(3\)。
#include <bits/stdc++.h>
using namespace std;
struct val
{
long long v,t;
}que[300000];
long long c,q,op,x,t,l=1,now,a[300000],s[300000],ql=1,qr=0;
void add()
{
scanf("%lld",&x);
a[++t]=x,s[t]=s[t-1]+x;
while(x>=que[qr].v&&ql<=qr)qr--;
que[++qr].v=x,que[qr].t=t;
}
void del()
{
scanf("%lld",&x);
now+=x;
while(now>=s[l]&&l<=t)l++;
}
void getnum()
{
scanf("%lld",&x);
x+=now;
long long bl=l,br=t;
while(bl<br)
{
long long mid=(bl+br)/2;
if(x<=s[mid])br=mid;
else bl=mid+1;
}
printf("%lld\n",a[br]-s[br]+x);
}
void getmax()
{
while(que[ql].t<l&&ql<=qr)ql++;
printf("%lld\n",que[ql].v);
}
int main()
{
scanf("%lld%lld",&c,&q);
for(int i=1;i<=q;i++)
{
scanf("%lld",&op);
if(op==1)add();
else if(op==2)del();
else if(op==3)getnum();
else if(op==4)getmax();
}
return 0;
}

浙公网安备 33010602011771号