bzoj3337 块状链表的各种操作

题目分析

在我做这道题之前,我一直以为维护数列已经是很BT的数据结构板子题了,现在发现我错了……
本蒟蒻的最长代码记录,甚至超过了立体八数码那道题。所以为了图代码更简洁,本蒟蒻牺牲了一些常数,所以这代码常数很大……
我们来分析一下各种操作吧(想看完整代码请直接翻到最下面)
好的,我们建立块,需要维护一些什么呢?原数组a,一个排序后的数组b,一个翻转标记rev,一个赋值标记same,一个块的大小sz,一个加法标记laz,一个块内元素和sum。
然后记得回收被删除的块。

基本操作

我们首先需要几个基本操作

pushup

用于排序,请尽量减少该函数的调用。

inline void up(int b1) {
    for(int i=1;i<=p[b1].sz;++i) p[b1].b[i]=p[b1].a[i];
    sort(p[b1].b+1,p[b1].b+p[b1].sz+1);
}

pushdown

下放标记,由于pushdown后必须要再pushup,所以也请尽量减少此函数调用。

inline void pd(int b1) {
    int tt=p[b1].sz;
    if(p[b1].rev) {
        for(int i=1;i<=tt/2;++i) swap(p[b1].a[i],p[b1].a[tt-i+1]);
        p[b1].rev=0;
    }
    if(p[b1].same) {for(int i=1;i<=tt;++i) p[b1].a[i]=p[b1].same;p[b1].same=0;}
    if(p[b1].laz) {for(int i=1;i<=tt;++i) p[b1].a[i]+=p[b1].laz;p[b1].laz=0;}
}

find

寻找第wz个数,在第b1个块的第b2个位置。

inline void find(int wz,int &b1,int &b2) {
    int js=0;b1=0;
    while(js+p[b1].sz<wz&&p[b1].nxt) js+=p[b1].sz,b1=p[b1].nxt;
    b2=wz-js;
}

split

将一个块b1分裂成大小为b2和p[b1].sz-b2的两个块,这个可以用于提取区间。

inline void spilt(int b1,int b2) {
    pd(b1);int kl=newjd();//newjd:建立一个新块,即从被回收队列里拿一个出来
    for(int i=b2+1;i<=p[b1].sz;++i) 
        p[kl].a[++p[kl].sz]=p[b1].a[i],p[kl].sum+=(LL)p[b1].a[i];
    p[b1].sum-=p[kl].sum,p[kl].nxt=p[b1].nxt,p[b1].nxt=kl,p[b1].sz=b2;
    up(b1),up(kl);
}

merge

合并b1和b1的下一个块,这个是在提取完区间后用于还原,以减小常数。

inline void merge(int b1) {
    int kl=p[b1].nxt; pd(b1),pd(kl);
    for(int i=1;i<=p[kl].sz;++i) p[b1].a[++p[b1].sz]=p[kl].a[i];
    p[b1].nxt=p[kl].nxt,p[b1].sum+=p[kl].sum;
    up(b1),del_node(kl);//del_node:将kl块清空,并回收kl这个编号
}

gs

依次合并可以合并的块,用于减小常数

inline void gs(int b1) {//看看后面是否有能合并的
    for(;b1;b1=p[b1].nxt)
        while(p[b1].nxt&&p[b1].sz+p[p[b1].nxt].sz<=sz) merge(b1);
}

分离区间

当我们要对一段区间进行操作的时候,先分离出这段区间,并得到区间左边的块xb1和右边的yb1。

inline void solve(int x,int y,int &xb1,int &yb1) {//分离出一段区间
    int kl;
    find(x-1,xb1,kl),spilt(xb1,kl),xb1=p[xb1].nxt;
    find(y,yb1,kl),spilt(yb1,kl);
}

操作

插入

inline void ins(int wz,int x) {//将数x插入到wz后面
    int b1,b2,kl,tt;
    find(wz,b1,b2);spilt(b1,b2),tt=b1;//先分裂开
    if(!p[p[b1].nxt].sz) b1=p[b1].nxt,b2=1;//当b1块已满或者b1=0时,将b1后移
    p[b1].a[++p[b1].sz]=x,p[b1].sum+=(LL)x;
    for(kl=1;kl<p[b1].sz;++kl) if(p[b1].b[kl]>x) break;
    for(int i=p[b1].sz;i>=kl;--i) p[b1].b[i]=p[b1].b[i-1];//插入排序
    p[b1].b[kl]=x;gs(tt);
}

删除

直接删除就可以了,没什么好说的。

inline void del(int x) {
    int b1,b2; find(x,b1,b2),pd(b1);
    p[b1].sum-=(LL)p[b1].a[b2],--p[b1].sz;
    for(int i=b2;i<=p[b1].sz;++i) p[b1].a[i]=p[b1].a[i+1];
    up(b1),gs(b1);
}

翻转

打翻转标记+交换指针。

inline void do3(int x,int y) {
    int xb1,yb1,tb1,kl; solve(x,y,xb1,yb1);
    find(x-1,tb1,kl),p[tb1].nxt=yb1;//牺牲常数...暴力找到xb1的上一个块,指针指向yb1
    for(int i=xb1;i!=p[yb1].nxt;i=p[i].nxt) st[++top]=i,p[i].rev^=1;
    p[st[1]].nxt=p[yb1].nxt;//xb1指向yb1的下一个
    while(top!=1) p[st[top]].nxt=st[top-1],--top;//区间内指针反向
    --top,gs(xb1);
}

旋转

交换两个区间就可以了,分离区间+指针交换。

inline void do4(int x,int y,int z) {
    int xb1,yb1,mb1,tb1,kl;
    solve(y-z+1,y,mb1,yb1),solve(x,y-z,xb1,mb1),find(x-1,tb1,kl);
    p[tb1].nxt=p[mb1].nxt,p[mb1].nxt=p[yb1].nxt,p[yb1].nxt=xb1;
    gs(xb1);
}

加法和修改为

提取区间+打标记+修改sum值

inline void do5(int x,int y,int z) {
    int xb1,yb1; solve(x,y,xb1,yb1);
    for(int i=xb1;i!=p[yb1].nxt;i=p[i].nxt) p[i].laz+=z,p[i].sum+=(LL)z*(LL)p[i].sz;
    gs(xb1);
}
inline void do6(int x,int y,int z) {
    int xb1,yb1; solve(x,y,xb1,yb1);
    for(int i=xb1;i!=p[yb1].nxt;i=p[i].nxt)
        p[i].laz=0,p[i].same=z,p[i].sum=(LL)z*(LL)p[i].sz;
    gs(xb1);
}

询问

询问和,最大最小值

提取区间+利用sum值和b数组求解,注意same和laz标记。

inline LL get7(int x,int y) {
    int xb1,yb1;LL re=0; solve(x,y,xb1,yb1);
    for(int i=xb1;i!=p[yb1].nxt;i=p[i].nxt) re+=p[i].sum;
    gs(xb1); return re;
}
inline int get8(int x,int y) {
    int xb1,yb1,mx=-inf,mi=inf; solve(x,y,xb1,yb1);
    for(int i=xb1;i!=p[yb1].nxt;i=p[i].nxt) {
        if(!p[i].sz) continue;//考虑same标记,不要每次询问都下传
        if(p[i].same) mi=min(mi,p[i].same+p[i].laz),mx=max(mx,p[i].same+p[i].laz);
        else mi=min(mi,p[i].b[1]+p[i].laz),mx=max(mx,p[i].b[p[i].sz]+p[i].laz);
    }
    gs(xb1);return mx-mi;
}

绝对值差

合理利用lowe_bound,注意加法标记

inline int get9(int x,int y,int z) {
    int xb1,yb1,re=inf; solve(x,y,xb1,yb1);
    for(int i=xb1;i!=p[yb1].nxt;i=p[i].nxt) {
        if(p[i].same) re=min(re,abs(z-p[i].same-p[i].laz));
        else {//注意加法标记
            int kl=lower_bound(p[i].b+1,p[i].b+1+p[i].sz,z-p[i].laz)-p[i].b;
            if(kl<=p[i].sz) re=min(re,p[i].b[kl]+p[i].laz-z);
            if(kl!=1) --kl,re=min(re,z-p[i].b[kl]-p[i].laz);
        }
    }
    gs(xb1);return re;
}

比val小的数的个数

合理利用lower_bound

inline int get11(int x,int y,int z) {
    int xb1,yb1,re=0; solve(x,y,xb1,yb1);
    for(int i=xb1;i!=p[yb1].nxt;i=p[i].nxt) {
        if(p[i].same) {if(p[i].same+p[i].laz<z) re+=p[i].sz;}
        else {
            int kl=lower_bound(p[i].b+1,p[i].b+1+p[i].sz,z-p[i].laz)-p[i].b;
            re+=kl-1;
        }
    }
    gs(xb1);return re;
}

第k小的数

二分+比val小的数的个数

inline int get10(int x,int y,int z) {//用二分
    int xb1,yb1,l=0,r=200000,re; solve(x,y,xb1,yb1);
    while(l<=r) {
        int mid=(l+r)>>1,js=0;
        for(int i=xb1;i!=p[yb1].nxt;i=p[i].nxt) {
            if(p[i].same) {if(p[i].same+p[i].laz<mid) js+=p[i].sz;}
            else {
                int kl=lower_bound(p[i].b+1,p[i].b+1+p[i].sz,mid-p[i].laz)-p[i].b;
                js+=kl-1;
            }
        }
        if(js>=z) r=mid-1;
        else l=mid+1,re=mid;
    }
    gs(xb1); return re;
}

完整代码

#include<bits/stdc++.h>
using namespace std;
inline int read() {
    int q=0,w=1;char ch=' ';
    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
    if(ch=='-') w=-1,ch=getchar();
    while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
    return q*w;
}
#define LL long long
const int N=30005,inf=1<<29;
int n,m,sz,top,st[N];
queue<int> q;//回收节点
struct node{int a[1003],b[1003],rev,laz,same,sz,nxt;LL sum;}p[N];
inline void pd(int b1) {
    int tt=p[b1].sz;
    if(p[b1].rev) {
        for(int i=1;i<=tt/2;++i) swap(p[b1].a[i],p[b1].a[tt-i+1]);
        p[b1].rev=0;
    }
    if(p[b1].same) {for(int i=1;i<=tt;++i) p[b1].a[i]=p[b1].same;p[b1].same=0;}
    if(p[b1].laz) {for(int i=1;i<=tt;++i) p[b1].a[i]+=p[b1].laz;p[b1].laz=0;}
}
inline void up(int b1) {
    for(int i=1;i<=p[b1].sz;++i) p[b1].b[i]=p[b1].a[i];
    sort(p[b1].b+1,p[b1].b+p[b1].sz+1);
}
inline void del_node(int b1)//删除一个节点
{p[b1].rev=p[b1].laz=p[b1].same=p[b1].nxt=p[b1].sz=p[b1].sum=0;q.push(b1);}
inline void find(int wz,int &b1,int &b2) {//寻找某个数所在的块
    int js=0;b1=0;
    while(js+p[b1].sz<wz&&p[b1].nxt) js+=p[b1].sz,b1=p[b1].nxt;
    b2=wz-js;
}
inline void merge(int b1) {//合并b1和其后面的块
    int kl=p[b1].nxt; pd(b1),pd(kl);
    for(int i=1;i<=p[kl].sz;++i) p[b1].a[++p[b1].sz]=p[kl].a[i];
    p[b1].nxt=p[kl].nxt,p[b1].sum+=p[kl].sum;
    up(b1),del_node(kl);
}
inline void gs(int b1) {//看看后面是否有能合并的
    for(;b1;b1=p[b1].nxt)
        while(p[b1].nxt&&p[b1].sz+p[p[b1].nxt].sz<=sz) merge(b1);
}
inline int newjd() {int re=q.front();q.pop();return re;}
inline void spilt(int b1,int b2) {
    pd(b1);int kl=newjd();
    for(int i=b2+1;i<=p[b1].sz;++i) 
        p[kl].a[++p[kl].sz]=p[b1].a[i],p[kl].sum+=(LL)p[b1].a[i];
    p[b1].sum-=p[kl].sum,p[kl].nxt=p[b1].nxt,p[b1].nxt=kl,p[b1].sz=b2;
    up(b1),up(kl);
}
inline void ins(int wz,int x) {
    int b1,b2,kl,tt;
    find(wz,b1,b2);spilt(b1,b2),tt=b1;
    if(!p[p[b1].nxt].sz) b1=p[b1].nxt,b2=1;
    p[b1].a[++p[b1].sz]=x,p[b1].sum+=(LL)x;
    for(kl=1;kl<p[b1].sz;++kl) if(p[b1].b[kl]>x) break;
    for(int i=p[b1].sz;i>=kl;--i) p[b1].b[i]=p[b1].b[i-1];
    p[b1].b[kl]=x;gs(tt);
}
inline void del(int x) {
    int b1,b2; find(x,b1,b2),pd(b1);
    p[b1].sum-=(LL)p[b1].a[b2],--p[b1].sz;
    for(int i=b2;i<=p[b1].sz;++i) p[b1].a[i]=p[b1].a[i+1];
    up(b1),gs(b1);
}
inline void solve(int x,int y,int &xb1,int &yb1) {//分离出一段区间
    int kl;
    find(x-1,xb1,kl),spilt(xb1,kl),xb1=p[xb1].nxt;
    find(y,yb1,kl),spilt(yb1,kl);
}
inline void do3(int x,int y) {
    int xb1,yb1,tb1,kl; solve(x,y,xb1,yb1);
    find(x-1,tb1,kl),p[tb1].nxt=yb1;
    for(int i=xb1;i!=p[yb1].nxt;i=p[i].nxt) st[++top]=i,p[i].rev^=1;
    p[st[1]].nxt=p[yb1].nxt;
    while(top!=1) p[st[top]].nxt=st[top-1],--top;
    --top,gs(xb1);
}
inline void do4(int x,int y,int z) {
    int xb1,yb1,mb1,tb1,kl;
    solve(y-z+1,y,mb1,yb1),solve(x,y-z,xb1,mb1),find(x-1,tb1,kl);
    p[tb1].nxt=p[mb1].nxt,p[mb1].nxt=p[yb1].nxt,p[yb1].nxt=xb1;
    gs(xb1);
}
inline void do5(int x,int y,int z) {
    int xb1,yb1; solve(x,y,xb1,yb1);
    for(int i=xb1;i!=p[yb1].nxt;i=p[i].nxt) p[i].laz+=z,p[i].sum+=(LL)z*(LL)p[i].sz;
    gs(xb1);
}
inline void do6(int x,int y,int z) {
    int xb1,yb1; solve(x,y,xb1,yb1);
    for(int i=xb1;i!=p[yb1].nxt;i=p[i].nxt)
        p[i].laz=0,p[i].same=z,p[i].sum=(LL)z*(LL)p[i].sz;
    gs(xb1);
}
inline LL get7(int x,int y) {
    int xb1,yb1;LL re=0; solve(x,y,xb1,yb1);
    for(int i=xb1;i!=p[yb1].nxt;i=p[i].nxt) re+=p[i].sum;
    gs(xb1); return re;
}
inline int get8(int x,int y) {
    int xb1,yb1,mx=-inf,mi=inf; solve(x,y,xb1,yb1);
    for(int i=xb1;i!=p[yb1].nxt;i=p[i].nxt) {
        if(!p[i].sz) continue;//注意以下几句
        if(p[i].same) mi=min(mi,p[i].same+p[i].laz),mx=max(mx,p[i].same+p[i].laz);
        else mi=min(mi,p[i].b[1]+p[i].laz),mx=max(mx,p[i].b[p[i].sz]+p[i].laz);
    }
    gs(xb1);return mx-mi;
}
inline int get9(int x,int y,int z) {
    int xb1,yb1,re=inf; solve(x,y,xb1,yb1);
    for(int i=xb1;i!=p[yb1].nxt;i=p[i].nxt) {
        if(p[i].same) re=min(re,abs(z-p[i].same-p[i].laz));
        else {//注意加法标记
            int kl=lower_bound(p[i].b+1,p[i].b+1+p[i].sz,z-p[i].laz)-p[i].b;
            if(kl<=p[i].sz) re=min(re,p[i].b[kl]+p[i].laz-z);
            if(kl!=1) --kl,re=min(re,z-p[i].b[kl]-p[i].laz);
        }
    }
    gs(xb1);return re;
}
inline int get10(int x,int y,int z) {//用二分
    int xb1,yb1,l=0,r=200000,re; solve(x,y,xb1,yb1);
    while(l<=r) {
        int mid=(l+r)>>1,js=0;
        for(int i=xb1;i!=p[yb1].nxt;i=p[i].nxt) {
            if(p[i].same) {if(p[i].same+p[i].laz<mid) js+=p[i].sz;}
            else {
                int kl=lower_bound(p[i].b+1,p[i].b+1+p[i].sz,mid-p[i].laz)-p[i].b;
                js+=kl-1;
            }
        }
        if(js>=z) r=mid-1;
        else l=mid+1,re=mid;
    }
    gs(xb1); return re;
}
inline int get11(int x,int y,int z) {
    int xb1,yb1,re=0; solve(x,y,xb1,yb1);
    for(int i=xb1;i!=p[yb1].nxt;i=p[i].nxt) {
        if(p[i].same) {if(p[i].same+p[i].laz<z) re+=p[i].sz;}
        else {
            int kl=lower_bound(p[i].b+1,p[i].b+1+p[i].sz,z-p[i].laz)-p[i].b;
            re+=kl-1;
        }
    }
    gs(xb1);return re;
}
int main()
{
    int x,y,z,bj;
    n=read(),sz=sqrt(n);
    for(int i=1;i<N;++i) q.push(i);
    for(int i=1;i<=n;++i) x=read(),ins(i-1,x);
    m=read();
    while(m--) {
        bj=read();
        if(bj==1) x=read(),z=read(),ins(x,z);
        else if(bj==2) x=read(),del(x);
        else if(bj==3) x=read(),y=read(),do3(x,y);
        else if(bj==4) x=read(),y=read(),z=read(),do4(x,y,z);
        else if(bj==5) x=read(),y=read(),z=read(),do5(x,y,z);
        else if(bj==6) x=read(),y=read(),z=read(),do6(x,y,z);
        else if(bj==7) x=read(),y=read(),printf("%lld\n",get7(x,y));
        else if(bj==8) x=read(),y=read(),printf("%d\n",get8(x,y));
        else if(bj==9) x=read(),y=read(),z=read(),printf("%d\n",get9(x,y,z));
        else if(bj==10) x=read(),y=read(),z=read(),printf("%d\n",get10(x,y,z));
        else x=read(),y=read(),z=read(),printf("%d\n",get11(x,y,z));
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值