P9588 队列

省流:队列 + multiset + 二分 + 树状数组

分析、实现

约定本文中子序列含义为题目队列中一段连续的元素。

我们很容易得到一种思路:由于每次插入的子序列中元素一定连续,因此我们只需要在队列中记录这个数列的最小值 $l$ 和最大值 $r$ 即可,使用结构体或 pair。这样这个子序列的元素个数 $size$ 就可以表示为 $r-l+1$。

对于操作二,从队头向后进行扫描出队,同时将总数 $x$ 减去已出队的子序列的 $size$。对于最后一个子序列,仅增加 $l$ 值。

对于操作三,这样就无需逐个元素扫描而可以直接跳跃。

对于操作四,在操作一、二中对每次发生变化的子序列最大值进行维护即可(下方代码中使用了 multiset)。

优化

可惜,这个思路是对的,时间复杂度是错的(75 pts)。对于每个操作三,时间复杂度达到了线性级别。问题在于,如果数据中出现了大量连续的操作三,我们每次都要从头开始扫描,这就造成了浪费。

不难发现,如果我们能维护子序列 $size$ 的前缀和,我们就可以通过二分来对查询位置 $z$ 进行查找。下方的代码中使用了树状数组进行维护,单次查询在 $\log^2$ 级别。

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=2e5+10;
struct S
{
int l,r;
}q[MAXN*2];
int head=1,tail=1;
int BIT[MAXN];
multiset<int>cnt;
void add(int x,int k)
{
for(int i=x;i<=2e5;i+=i&(-i))BIT[i]+=k;
}
int query(int x)
{
int ret=0;
for(int i=x;i;i-=i&(-i))ret+=BIT[i];
return ret;
}
signed main()
{
ios::sync_with_stdio(false);
int c,t;
cin>>c>>t;
while(t--)
{
int op;
int x;
cin>>op;
switch(op)
{
case 1:
{
cin>>x;
add(tail,x);
q[tail++]={1,x};
cnt.insert(x);
break;
}
case 2:
{
cin>>x;
while(x>0)
{
if(x>=q[head].r-q[head].l+1)
{
x-=(q[head].r-q[head].l+1);
add(head,-(q[head].r-q[head].l+1));
cnt.erase(cnt.find(q[head].r));
head++;
}
else
{
q[head].l+=x;
add(head,-x);
break;
}
}
break;
}
case 3:
{
cin>>x;
int l=head,r=tail;
while(l<r)
{
int mid=l+r>>1;
if(query(mid)<x)l=mid+1;
else r=mid;
}
cout<<q[l].l+x-query(l-1)-1<<'\n';
break;
}
case 4:
{
cout<<*(--cnt.end())<<'\n';
break;
}
}
}
return 0;
}
posted @   H2ptimize  阅读(11)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示