【学习笔记】[集训队互测 2018] 完美的队列

Part 1

有点难😅

考虑暴力做法的本质🤔

发现瓶颈在于找到所有的作用区间,观察性质发现作用的区间和之前队列中有多少元素是无关的

用分块优化之(类似于 大步小步),可以做到 O ( n n log ⁡ n ) O(n\sqrt{n}\log n) O(nn logn)

考虑优化掉 log ⁡ \log log🤔

发现对于一段相同的区间,其对应的 e d i ed_i edi(即作用区间的右端点)是单调的,因此可以考虑将每个 [ l , r ] [l,r] [l,r]拆分成若干个整块和至多 2 2 2个散块,然后对所有块取 max ⁡ \max max就能得到 e d i ed_i edi

对于整块,用双指针暴力移动之,如果仍用线段树来维护那么复杂度不变;但是发现对于整块的修改可以直接打标记,而每个修改至多拆分成两个散块,因此均摊正确;

对于散块,考虑拆分成 单个队列,发现指针的单次移动为 O ( 1 ) O(1) O(1),因此复杂度正确;

总复杂度 O ( n n ) O(n\sqrt{n}) O(nn )

Part 2

可以把操作区间在线段树上划分成 log ⁡ n \log n logn个区间,然后每个位置有一个限制 a i a_i ai,表示如果每个位置的 a i a_i ai都减成 ≤ 0 \le 0 0了那么作用区间就找到了。考虑建立两颗线段树,一颗维护上述限制,对于散块(即不完整的区间修改)就暴力在上面改;再建立一颗以时间为下标的线段树,整块的操作就打在这颗线段树上,查询的时候在上面二分一下即可。注意要双指针维护

因为散块只有 n log ⁡ n n\log n nlogn个,所以这个做法的复杂度是 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n)

听说甚至跑不过分块。太逊了。

Part 3

PKUWC2024 DAY2 T3:

编号为 1 ∼ n 1\sim n 1n 的栈,有 m m m 次操作形如:

1 l r x y:对编号为 l ∼ r l\sim r lr 的栈插入 x x x y y y
2 l r w:对编号为 l ∼ r l\sim r lr 的栈分别执行 w w w 次弹栈操作。
3 k p q:求编号为 k k k 的栈中第 p ∼ q p \sim q pq 个元素的和,没有的元素视为 0 0 0

n , m ≤ 1 0 5 n,m\le 10^5 n,m105

首先吐槽一句,个人不是很喜欢这种数据结构题。

首先,因为是单点询问,所以可以考虑扫描位置,维护时间轴上发生的操作(说白了就是把时间看成下标),然后对时间分块。显然可以做到 O ( n n log ⁡ n ) O(n\sqrt{n\log n}) O(nnlogn )

可以用单侧递归线段树做(楼房重建)。对于线段树上一个节点,维护区间内所有操作的效果,即push数目,pop数目,push的数之和。查询的时候从后往前处理,记录时间后方有几个pop,如果pop数大于右子树的push数就不用递归右子树,否则左子树的信息可以用作差求出,根据要查询的前缀的长度递归即可。复杂度 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n)

upd on 2024/2/3 :添加了线段树做法。

#include<bits/stdc++.h>
#define fi first
#define se second
#define ll long long
#define pb push_back
#define db double
#define inf 0x3f3f3f3f
using namespace std;
const int N=1e5+5;
const int B=305;
int n,m,a[N];
int bl[N];
struct query{
    int l,r,x;
}q[N];
int res[N],tag,ps,L,R,len;
struct node{
    int max,tag;
}t[N<<2];
void pushup(int p){
    t[p].max=max(t[p<<1].max,t[p<<1|1].max);
}
void build(int p,int l,int r){
    t[p].tag=0;
    if(l==r){
        t[p].max=a[L+l-1];
        return;
    }int mid=l+r>>1;
    build(p<<1,l,mid),build(p<<1|1,mid+1,r);
    pushup(p);
}
void add(int p,int x){
    t[p].max+=x,t[p].tag+=x;
}
void pushdown(int p){
    if(t[p].tag){
        add(p<<1,t[p].tag),add(p<<1|1,t[p].tag),t[p].tag=0;
    }
}
void modify(int p,int l,int r,int ql,int qr,int x){
    if(ql<=l&&r<=qr){
        add(p,x);
        return;
    }int mid=l+r>>1;pushdown(p);
    if(ql<=mid)modify(p<<1,l,mid,ql,qr,x);
    if(mid<qr)modify(p<<1|1,mid+1,r,ql,qr,x);
    pushup(p);
}
void Add(int pos,int f){
    if(pos>m)return;
    int l=q[pos].l,r=q[pos].r,x=bl[l],y=bl[r];
    if(x==y&&x==ps){
        modify(1,1,len,l-L+1,r-L+1,f);
    }
    if(x!=y&&x==ps){
        modify(1,1,len,l-L+1,len,f);
    }
    if(x!=y&&y==ps){
        modify(1,1,len,1,r-L+1,f);
    }
    if(x<ps&&ps<y){
        tag+=f;
    }
}
int sum[N],rk[N];
int s[N],cnt;
vector<int>nums;
int c[N];
int lastans;
vector<int>vec1[N];
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n>>m;for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++)bl[i]=(i-1)/B+1;
    for(int i=1;i<=m;i++){
        cin>>q[i].l>>q[i].r>>q[i].x;
    }
    for(int i=1;i<=bl[n];i++){
        ps=i;L=(i-1)*B+1,R=min(n,i*B),len=R-L+1;
        build(1,1,len);
        int ql=1,qr=0;nums.clear();tag=0;
        for(int j=1;j<=m;j++){
            int l=q[j].l,r=q[j].r;
            int x=bl[l],y=bl[r];
            sum[j]=sum[j-1];
            if(x<i&&i<y){
                while(qr<j)Add(++qr,-1);
                while(ql<j)Add(ql++,1);
                while(qr<=m&&t[1].max+tag>=0)Add(++qr,-1);
                res[j]=max(res[j],qr-1);sum[j]++;rk[sum[j]]=j;
            }
            if(x==y&&x==ps)nums.pb(j);
            if(x!=y&&x==ps)nums.pb(j);
            if(x!=y&&y==ps)nums.pb(j);
        }
        //处理单点
        for(int j=L;j<=R;j++){
            cnt=0;
            for(auto e:nums){
                int l=q[e].l,r=q[e].r;
                if(l<=j&&j<=r)s[++cnt]=e;
            }s[cnt+1]=m+1;
            int ql=0;
            for(int k=1;k<=cnt;k++){
                if(cnt-k+1+sum[m]-sum[s[k]]<=a[j]){
                    res[s[k]]=m;
                    continue;
                }
                ql=max(ql,k);
                while(ql<=cnt&&sum[s[ql]]-sum[s[k]]+ql-k+1<=a[j]){
                    ql++;
                }
                if(sum[s[ql]-1]-sum[s[k]]+ql-k==a[j]){
                    res[s[k]]=max(res[s[k]],s[ql]-1);
                }
                else{
                    res[s[k]]=max(res[s[k]],rk[a[j]+sum[s[k]]+k-ql+1]-1);
                }
            }
        }
    }
    for(int i=1;i<=m;i++){
        int x=q[i].x;
        vec1[res[i]+1].pb(x);
    }
    for(int i=1;i<=m;i++){
        int x=q[i].x;
        if(++c[x]==1)lastans++;
        for(auto e:vec1[i])if(--c[e]==0)lastans--;
        cout<<lastans<<"\n";
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值