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; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】