很早就想写的东西,最近才有时间写。主要是不想改错

树状数组

支持维护前缀和或者是前缀最大值之类的东西,支持单点修改。

基本只有维护前缀才会想到这个,其余的情况用线段树替代。

Code

int c[N],n;
int ask(int x)
{
    int ans=0;
    for(;x;x-=lowbit(x))ans+=c[x];
    return ans;
}
void add(int x,int y)
{
    for(;x<=n;x+=lowbit(x))c[x]+=y;
}

线段树

用处多的很,支持区间修改和区间查询,以及可以在线段树上二分。

扫描线可以在维护矩形的周长并或者面积并时用到。

Code

int a[N];
struct Segment_Tree
{
    struct sss
    {
        int l,r;
        long long sum,add;
        #define l(x) tree[x].l
        #define r(x) tree[x].r
        #define sum(x) tree[x].sum
        #define add(x) tree[x].add
    }tree[1000005];
    void build(int x,int l,int r)
    {
        l(x)=l;r(x)=r;
        if(l==r)
        {
            sum(x)=a[l];
            return;
        }
        int mid=(l(x)+r(x))>>1;
        build(x*2,l,mid);
        build(x*2+1,mid+1,r);
        sum(x)=sum(x*2)+sum(x*2+1);
    }
    void spread(int x)
    {
        if(add(x))
        {
            add(x*2)+=add(x);
            add(x*2+1)+=add(x);
            sum(x*2)+=add(x)*(r(x*2)-l(x*2)+1);
            sum(x*2+1)+=add(x)*(r(x*2+1)-l(x*2+1)+1);
            add(x)=0;
        }
    }
    void change(int x,int l,int r,int p)
    {
        if(l<=l(x)&&r(x)<=r)
        {
            sum(x)+=p*(r(x)-l(x)+1);
            add(x)+=p;
            return;
        }
        spread(x);
        int mid=(l(x)+r(x))>>1;
        if(l<=mid)change(x*2,l,r,p);
        if(mid<r)change(x*2+1,l,r,p);
        sum(x)=sum(x*2)+sum(x*2+1);
    }
    long long ask(int x,int l,int r)
    {
        if(l<=l(x)&&r(x)<=r)return sum(x);
        spread(x);
        int mid=(l(x)+r(x))>>1;
        long long res=0;
        if(l<=mid)res+=ask(x*2,l,r);
        if(mid<r)res+=ask(x*2+1,l,r);
        return res;
    }
}T1;

分块

将序列分成若干块,大段维护,局部朴素。块长一般取 $\sqrt{n}$。

具体来说就是对于操作 $[l,r]$,设 $l$ 在块 $bl$ ,$r$ 在 $br$ 。

  • 如果 $br-bl=1$ 就直接暴力。

  • 否则对于 $[bl+1,br-1]$ 的块,直接在块上查询/修改;

    然后暴力查询/修改剩下的部分。

Code P4168 [Violet]蒲公英

#include<algorithm>
#include<cmath>
#include<cstring>
#include<iostream>
#include<map>
using namespace std;
int T,L,n,a[50005],s[505][50005],f[505][505],d[50005],sum,cnt,x,g[50005];
map<int,int>mp;
int main()
{
    scanf("%d%d",&n,&T);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        d[i]=a[i];
    }
    sort(d+1,d+n+1);
    sum=unique(d+1,d+n+1)-1-d;
    for(int i=1;i<=sum;i++)mp[d[i]]=i;
    for(int i=1;i<=n;i++)a[i]=mp[a[i]];
    L=sqrt(n);
    cnt=(n-1)/L+1;
    for(int i=1;i<=cnt;i++)
    {
        for(int j=(i-1)*L+1;j<=min(i*L,n);j++)s[i][a[j]]++;
        for(int j=1;j<=sum;j++)s[i][j]+=s[i-1][j];
    }
    for(int i=1;i<=cnt;i++)
    {
        for(int j=i;j<=cnt;j++)
        {
            int mx=f[i][j-1];
            for(int k=(j-1)*L;k<=min(j*L,n);k++)if((s[j][a[k]]-s[i-1][a[k]]>s[j][mx]-s[i-1][mx])||(s[j][a[k]]-s[i-1][a[k]]==s[j][mx]-s[i-1][mx]&&a[k]<mx))mx=a[k];
            f[i][j]=mx;
        }
    }
    while(T--)
    {
        memset(g,0,sizeof(g));
        int l,r;
        scanf("%d%d",&l,&r);
        l=((l+x-1)%n)+1;
        r=((r+x-1)%n)+1;
        if(l>r)swap(l,r);
        int bl=(l-1)/L+1,br=(r-1)/L+1,mx;
        if(br-bl<=1)
        {
            for(int i=l;i<=r;i++)g[a[i]]++;
            for(int i=l;i<=r;i++)if(g[a[i]]>g[mx]||(g[a[i]]==g[mx]&&a[i]<mx))mx=a[i];
        }
        else
        {
            for(int i=l;i<=L*bl;i++)g[a[i]]++;
            for(int i=L*(br-1)+1;i<=r;i++)g[a[i]]++;
            mx=f[bl+1][br-1];
            for(int i=l;i<=L*bl;i++)
            {
                int pre=g[mx]+s[br-1][mx]-s[bl][mx],now=g[a[i]]+s[br-1][a[i]]-s[bl][a[i]];
                if(now>pre||(now==pre&&mx>a[i]))mx=a[i];
            }
            for(int i=L*(br-1)+1;i<=r;i++)
            {
                int pre=g[mx]+s[br-1][mx]-s[bl][mx],now=g[a[i]]+s[br-1][a[i]]-s[bl][a[i]];
                if(now>pre||(now==pre&&mx>a[i]))mx=a[i];
            }
        }
        x=d[mx];
        printf("%d\n",x);
    }
}

以及,分块可以维护插入和删除操作,我们可以将分块存在一个链表上面,块首单独存在另一个一个链表上面,找块的时候直接在这个链表上面跳即可。

  • 对于插入,我们直接插在所对应的块里面,如果该块长超过了 $2\sqrt{n}$,就把它分成两部分;
  • 对于删除,直接删即可,如果该块为空,删除这个块。

莫队

对询问进行分块,将左端点排序,分成 $\sqrt{n}$ 块,每块内部再按照右端点排序。

这样就可以以上次的询问为基础,在 $\sqrt{n}$ 的时间里更新答案。

Code P1494 [国家集训队] 小 Z 的袜子
#include<algorithm>
#include<cmath>
#include<iostream>
#define int long long
using namespace std;
struct sss
{
    int l,r,id;
}a[100005];
int s[100005],n,T,ans[100005][2],an1,an2,f[1005][50005],L,cnt,sum[100005],l,r;
bool cmp(sss a,sss s)
{
    int x=(a.l-1)/L+1;
    int y=(s.l-1)/L+1;
    if(x==y)return a.r>s.r;
    return x<y;
}
void add(int x)
{
    sum[x]++;
    if(sum[x]>1)an1=an1+sum[x]*(sum[x]-1)-(sum[x]-1)*(sum[x]-2);
}
void del(int x)
{
    sum[x]--;
    if(sum[x]>0)an1=an1+sum[x]*(sum[x]-1)-(sum[x]+1)*sum[x];
}
void get(int x,int y,int i)
{
    if(x==0)
    {
        ans[i][0]=0;
        ans[i][1]=1;
        return;
    }
    int gcd=__gcd(x,y);
    ans[i][0]=x/gcd;
    ans[i][1]=y/gcd;
}
signed main()
{
    scanf("%lld%lld",&n,&T);
    L=sqrt(T);
    cnt=(T-1)/L+1;
    for(int i=1;i<=n;i++)scanf("%lld",&s[i]);
    for(int i=1;i<=T;i++)scanf("%lld%lld",&a[i].l,&a[i].r),a[i].id=i;
    sort(a+1,a+T+1,cmp);
    for(int i=a[1].l;i<=a[1].r;i++)add(s[i]);
    an2=(a[1].r-a[1].l+1)*(a[1].r-a[1].l);
    get(an1,an2,a[1].id);
    l=a[1].l;r=a[1].r;
    for(int i=2;i<=T;i++)
    {
        while(l<a[i].l)del(s[l++]);
        while(l>a[i].l)add(s[--l]);
        while(r<a[i].r)add(s[++r]);
        while(r>a[i].r)del(s[r--]);
        an2=(a[i].r-a[i].l+1)*(a[i].r-a[i].l);
        if(l==r)ans[a[i].id][0]=0,ans[a[i].id][1]=1;
        else get(an1,an2,a[i].id);
    }
    for(int i=1;i<=T;i++)printf("%lld/%lld\n",ans[i][0],ans[i][1]);
}

平衡树

终于到了平衡树了。

FHQ,也就是无旋 Treap ,与 Treap 相似,都是运用堆性质来维护树高。

FHQ 的核心主要是两种操作:split 和 merge。

  • splitv 是将 FHQ 分裂成两颗树 $x$ 和 $y$,满足 $x$ 的所有节点权值均小于 $key$ 小于 $y$ 中节点。($key$ 自己定,大于小于看题目情况)

    splits 是将 FHQ 分裂成两颗树 $x$ 和 $y$,满足 $x$ 的节点个数为 $s$。($s$ 自己定)

  • merge 是将 $x$ 和 $y$ 合并成一颗树,在中序遍历中,$x$ 中节点的位置将在 $y$ 的节点的右边。

这是详解

有了这个,剩下的都可以自己推了。

Code

#include<iostream>
using namespace std;
struct FHQ
{
    int cnt,root;
    struct sss
    {
        int r,l,s,w,v;
        #define l(x) tree[x].l
        #define r(x) tree[x].r
        #define s(x) tree[x].s
        #define w(x) tree[x].w
        #define v(x) tree[x].v
    }tree[100005];
    void build(){cnt=0;}
    int New(int x)
    {
        w(++cnt)=rand();
        v(cnt)=x;
        s(cnt)=1;
        return cnt;
    }
    void pushup(int x){s(x)=s(l(x))+s(r(x))+1;}
    void splitv(int root,int key,int &x,int &y)
    {
        if(!root)x=y=0;
        else if(v(root)<=key)
        {
            x=root;
            splitv(r(root),key,r(x),y);
            pushup(root);
        }
        else
        {
            y=root;
            splitv(l(root),key,x,l(y));
            pushup(root);
        }
    }
    void splits(int root,int s,int &x,int &y)
    {
        if(!root)x=y=0;
        else if(s(l(root))<s)
        {
            x=root;
            splits(r(root),s-s(l(root))-1,r(x),y);
            pushup(root);
        }
        else
        {
            y=root;
            splits(l(root),s,x,l(y));
            pushup(root);
        }
    }
    int merge(int x,int y)
    {
        if(!x||!y)return x^y;
        else if(w(x)>w(y))
        {
            r(x)=merge(r(x),y);
            pushup(x);
            return x;
        }
        else
        {
            l(y)=merge(x,l(y));
            pushup(y);
            return y;
        } 
    }
    void insert(int key)
    {
        int x,y;
        splitv(root,key-1,x,y);
        root=merge(merge(x,New(key)),y);
    }
    void remove(int key)
    {
        int x,y,z;
        splitv(root,key,x,z);
        splitv(x,key-1,x,y);
        if(y)y=merge(l(y),r(y));
        root=merge(merge(x,y),z);
    }
    int v2r(int key)
    {
        int x,y,ans;
        splitv(root,key-1,x,y);
        ans=s(x)+1;
        root=merge(x,y);
        return ans;
    }
    int r2v(int key)
    {
        int x,y,ans,now;
        splits(root,key,x,y);
        now=x;
        while(r(now))now=r(now);
        ans=v(now);
        root=merge(x,y);
        return ans;
    }
    int pre(int key)
    {
        int x,y,now,ans;
        splitv(root,key-1,x,y);
        now=x;
        while(r(now))now=r(now);
        ans=v(now);
        root=merge(x,y);
        return ans;
    }
    int nxt(int key)
    {
        int x,y,now,ans;
        splitv(root,key,x,y);
        now=y;
        while(l(now))now=l(now);
        ans=v(now);
        root=merge(x,y);
        return ans;
    }
}T1;
int T;
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        int op,x;
        scanf("%d%d",&op,&x);
        if(op==1)T1.insert(x);
        else if(op==2)T1.remove(x);
        else if(op==3)printf("%d\n",T1.v2r(x));
        else if(op==4)printf("%d\n",T1.r2v(x));
        else if(op==5)printf("%d\n",T1.pre(x));
        else printf("%d\n",T1.nxt(x));
    }
}

文艺平衡树

根据 FHQ 的特性,FHQ 是可以维护区间操作的,我们只需要将通篇使用 splits 即可。

Code
#include<iostream>
using namespace std;
struct FHQ
{
    int cnt,root;
    struct sss
    {
        int r,l,s,w,v,tag;
        #define l(x) tree[x].l
        #define r(x) tree[x].r
        #define s(x) tree[x].s
        #define w(x) tree[x].w
        #define v(x) tree[x].v
        #define tag(x) tree[x].tag
    }tree[100005];
    void build(){cnt=0;}
    int New(int x)
    {
        w(++cnt)=rand();
        v(cnt)=x;
        s(cnt)=1;
        return cnt;
    }
    void pushup(int x){s(x)=s(l(x))+s(r(x))+1;}
    void pushdown(int x)
    {
        if(tag(x))
        {
            swap(l(x),r(x));
            tag(l(x))^=1;
            tag(r(x))^=1;
            tag(x)=0;
        }
    }
    void splits(int root,int s,int &x,int &y)
    {
        pushdown(root);
        if(!root)x=y=0;
        else if(s(l(root))<s)
        {
            x=root;
            splits(r(root),s-s(l(root))-1,r(x),y);
            pushup(root);
        }
        else
        {
            y=root;
            splits(l(root),s,x,l(y));
            pushup(root);
        }
    }
    int merge(int x,int y)
    {
        if(!x||!y)return x^y;
        else if(w(x)>w(y))
        {
            pushdown(x);
            r(x)=merge(r(x),y);
            pushup(x);
            return x;
        }
        else
        {
            pushdown(y);
            l(y)=merge(x,l(y));
            pushup(y);
            return y;
        } 
    }
    void insert(int key)
    {
        int x,y;
        splits(root,key-1,x,y);
        root=merge(merge(x,New(key)),y);
    }
    void remove(int key)
    {
        int x,y,z;
        splits(root,key,x,z);
        splits(x,key-1,x,y);
        if(y)y=merge(l(y),r(y));
        root=merge(merge(x,y),z);
    }
    void reverse(int l,int r)
    {
        int x,y,p;
        splits(root,r,x,y);
        splits(x,l-1,x,p);
        tag(p)^=1;
        root=merge(merge(x,p),y);
    }
    void print(int x)
    {
        pushdown(x);
        if(l(x))print(l(x));
        printf("%d ",v(x));
        if(r(x))print(r(x));
    }
}T1;
int T,n;
int main()
{
    scanf("%d%d",&n,&T);
    for(int i=1;i<=n;i++)T1.root=T1.merge(T1.root,T1.New(i));
    while(T--)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        T1.reverse(l,r);
    }
    T1.print(T1.root);
}

二逼平衡树

用于在带修情况下在区间中查询第 $k$ 大等信息。

考虑线段树套平衡树,外层为值域,内层为下标。

删除和插入直接在线段树跑,在线段树中的平衡树中插入/删除下标即可。

前缀和后继可以通过查询第 $k$ 大和查询排名实现。

排名直接在线段树上面跑有多少在 $[l,r]$ 的树小于 $key$。

第 $k$ 大在线段树上二分即可。

Code
#include<iostream>
using namespace std;
int cnt;
struct sss
{
    int r,l,s,w,v;
    #define l(x) tree[x].l
    #define r(x) tree[x].r
    #define s(x) tree[x].s
    #define w(x) tree[x].w
    #define v(x) tree[x].v
}tree[6000005];
struct FHQ
{
    int root;
    int New(int x)
    {
        w(++cnt)=rand();
        v(cnt)=x;
        s(cnt)=1;
        return cnt;
    }
    void pushup(int x){s(x)=s(l(x))+s(r(x))+1;}
    void splitv(int root,int key,int &x,int &y)
    {
        if(!root)x=y=0;
        else if(v(root)<=key)
        {
            x=root;
            splitv(r(root),key,r(x),y);
            pushup(root);
        }
        else
        {
            y=root;
            splitv(l(root),key,x,l(y));
            pushup(root);
        }
    }
    int merge(int x,int y)
    {
        if(!x||!y)return x^y;
        else if(w(x)>w(y))
        {
            r(x)=merge(r(x),y);
            pushup(x);
            return x;
        }
        else
        {
            l(y)=merge(x,l(y));
            pushup(y);
            return y;
        } 
    }
    void insert(int key)
    {
        int x,y;
        splitv(root,key-1,x,y);
        root=merge(merge(x,New(key)),y);
    }
    void remove(int key)
    {
        int x,y,z;
        splitv(root,key,x,z);
        splitv(x,key-1,x,y);
        if(y)y=merge(l(y),r(y));
        root=merge(merge(x,y),z);
    }
    int v2r(int key)
    {
        int x,y,ans;
        splitv(root,key-1,x,y);
        ans=s(x)+1;
        root=merge(x,y);
        return ans;
    }
    int nxt(int key)
    {
        int x,y,now,ans;
        splitv(root,key,x,y);
        now=y;
        while(l(now))now=l(now);
        ans=v(now);
        root=merge(x,y);
        return ans;
    }
}T[6000005];
struct Segment_Tree
{
    struct sss
    {
        int l,r,sum;
        #define l(x) tree[x].l
        #define r(x) tree[x].r
        #define sum(x) tree[x].sum
    }tree[5000005];
    int root,cnt,N=1e9,a[5000005];
    int build()
    {
        cnt++;
        return cnt;
    }
    void change(int x,int l,int r,int p,int o)
    {
        if(l==r)
        {
            if(!T[x].root)
            {
                T[x].insert(-2147483647);
                T[x].insert(2147483647);
            }
            if(o<0)T[x].remove(-o),sum(x)--;
            else T[x].insert(o),sum(x)++;
            return;
        }
        int mid=(l+r)>>1;
        if(p<=mid)
        {
            if(!l(x))l(x)=build();
            change(l(x),l,mid,p,o);
        }
        else 
        {
            if(!r(x))r(x)=build();
            change(r(x),mid+1,r,p,o);
        }
        if(!T[x].root)
        {
            T[x].insert(-2147483647);
            T[x].insert(2147483647);
        }
        if(o<0)T[x].remove(-o);
        else T[x].insert(o);
        sum(x)=sum(l(x))+sum(r(x));
    }
    int ask_sum(int x,int l,int r,int l1,int r1,int l2,int r2)
    {
        if(l2<=l&&r<=r2)
        {
            int sum=0;
            sum-=T[x].v2r(l1);
            sum+=T[x].v2r(T[x].nxt(r1));
            return sum;
        }
        int mid=(l+r)>>1,sum=0;
        if(l2<=mid)
        {
            if(!l(x))l(x)=build();
            sum+=ask_sum(l(x),l,mid,l1,r1,l2,r2);
        }
        if(mid<r2)
        {
            if(!r(x))r(x)=build();
            sum+=ask_sum(r(x),mid+1,r,l1,r1,l2,r2);
        }
        return sum;
    }
    int ask_rank(int l1,int r1,int p)
    {
        return ask_sum(root,1,N,l1,r1,1,p-1)+1;
    }
    int ask_val(int x,int l,int r,int l1,int r1,int p)
    {
        if(l==r)return sum(x)?l:-1;
        int sum=0;
        sum-=T[l(x)].v2r(l1);
        sum+=T[l(x)].v2r(T[l(x)].nxt(r1));
        int mid=(l+r)>>1;
        if(p<=sum)
        {
            if(!l(x))l(x)=build();
            return ask_val(l(x),l,mid,l1,r1,p);
        }
        else
        {
            if(!r(x))r(x)=build();
            return ask_val(r(x),mid+1,r,l1,r1,p-sum);
        }
    }
    void update(int pos,int v)
    {
        change(root,1,N,a[pos],-pos);
        a[pos]=v;
        change(root,1,N,a[pos],pos);
    }
    int pre(int l,int r,int p)
    {
        if(p==1)return -2147483646;
        int rnk=ask_rank(l,r,p)-1;
        if(!rnk)return -2147483646;
        int ans=ask_val(root,1,N,l,r,rnk);
        return ans==-1?-2147483646:ans;
    }
    int nxt(int l,int r,int p)
    {
        int rnk=ask_rank(l,r,p+1);
        int ans=ask_val(root,1,N,l,r,rnk);
        return ans==-1?2147483648:ans;
    }
}T1;
int Q,n;
int main()
{
    scanf("%d%d",&n,&Q);
    T1.root=T1.build();
    for(int i=1;i<=n;i++)
    {
        int q;
        scanf("%d",&q);
        q++;
        T1.change(T1.root,1,T1.N,q,i);
        T1.a[i]=q;
    }
    while(Q--)
    {
        int op,l,r,x,pos;
        scanf("%d",&op);
        if(op==1)
        {
            scanf("%d%d%d",&l,&r,&x);
            printf("%d\n",T1.ask_rank(l,r,x+1));
        }
        else if(op==2)
        {
            scanf("%d%d%d",&l,&r,&x);
            printf("%d\n",T1.ask_val(T1.root,1,T1.N,l,r,x)-1);
        }
        else if(op==3)
        {
            scanf("%d%d",&pos,&x);
            T1.update(pos,x+1);
        }
        else if(op==4)
        {
            scanf("%d%d%d",&l,&r,&x);
            printf("%d\n",T1.pre(l,r,x+1)-1);
        }
        else
        {
            scanf("%d%d%d",&l,&r,&x);
            printf("%d\n",T1.nxt(l,r,x+1)-1);
        }
    }
}

CDQ

处理三维偏序的时候用,可以类比一下归并排序求逆序对,就是多了一个树状数组处理多出来的一维偏序。

记得树状数组清零的时候用撤销操作,否则时间复杂度直接跑到 $n^2\log^2{n}$。

Code P3810 【模板】三维偏序(陌上花开)

#include<algorithm>
#include<iostream>
#define lowbit(x) (x&-x)
using namespace std;
struct sss
{
    int a,s,d,cnt,ans;
}a[1000005],s[1000005];
int n,k,d[1000005],c[1000005],m;
inline int ask(int x)
{
    int ans=0;
    for(;x;x-=lowbit(x))ans+=c[x];
    return ans;
}
inline void add(int x,int y)
{
    for(;x<=k;x+=lowbit(x))c[x]+=y;
}
bool cmp1(sss a,sss s){return a.a!=s.a?a.a<s.a:a.s!=s.s?a.s<s.s:a.d<s.d;}
bool cmp2(sss a,sss s){return a.s!=s.s?a.s<s.s:a.d<s.d;}
void cdq(int l,int r)
{
    if(l==r)return;
    int mid=(l+r)>>1;
    cdq(l,mid);
    cdq(mid+1,r);
    sort(s+l,s+mid+1,cmp2);
    sort(s+mid+1,s+r+1,cmp2);
    int j=l;
    for(int i=mid+1;i<=r;i++)
    {
        while(s[j].s<=s[i].s&&j<=mid)
        {
            add(s[j].d,s[j].cnt);
            j++;
        }
        s[i].ans+=ask(s[i].d);
    }
    for(int i=l;i<j;i++)add(s[i].d,-s[i].cnt);
}
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)scanf("%d%d%d",&a[i].a,&a[i].s,&a[i].d);
    sort(a+1,a+n+1,cmp1);
    int num=0;
    for(int i=1;i<=n;i++)
    {
        num++;
        if(a[i].a!=a[i+1].a||a[i].s!=a[i+1].s||a[i].d!=a[i+1].d)
        {
            s[++m].a=a[i].a;
            s[m].s=a[i].s;
            s[m].d=a[i].d;
            s[m].cnt=num;
            num=0;
        }
    }
    cdq(1,m);
    for(int i=1;i<=m;i++)d[s[i].ans+s[i].cnt-1]+=s[i].cnt;
    for(int i=0;i<n;i++)printf("%d\n",d[i]);
}

后记

写的有点潦草,有空再补。