【日记】12.9

12.9日记

对顶堆

功能:动态维护区间第k大,支持插入和删除。小根堆储存大数,大根堆储存小数。

  1. P1801:插入+输出第k大。
#include
using namespace std;
const int M=2e5+20;
int a[M];
priority_queue qb;
priority_queue,greater > qs;
inline void operate(int num){
    while(qb.size()num)
        qs.push(qb.top()),qb.pop();
}
inline void insert(int x){
    if (!qs.empty()&&x>qs.top())
        qs.push(x);
    else
        qb.push(x);
}
int main(){
    int m,n;
    scanf("%d%d",&m,&n);
    for(int i=1;i<=m;++i)
        scanf("%d",&a[i]);
    int q=0,p=0;
    for(int i=1;i<=n;++i){
        int ca;
        scanf("%d",&ca),++q;
        while(p

主席树

  1. P1801:

拿大炮打苍蝇……

#include
using namespace std;
#define mid (l+r)/2
const int M=8e6+20,Mm=2e5+20;
int cnt,a[Mm],b[Mm],v[M],L[M],R[M],root[Mm];
unordered_map rev;
int build(int l,int r){
    int rt=++cnt;
    v[rt]=0;
    if (l==r)
        return rt;
    build(l,mid),build(mid+1,r);
    return rt;
}
int operate(int idp,int l,int r,int pos,int x){
    int rt=++cnt;
    L[rt]=L[idp],R[rt]=R[idp],v[rt]=v[idp]+x;
    if (l==r)
        return rt;
    if (pos<=mid)
        L[rt]=operate(L[idp],l,mid,pos,x);
    else
        R[rt]=operate(R[idp],mid+1,r,pos,x);
    return rt;
}
int query(int lid,int nid,int l,int r,int k){
    if (l==r)
        return l;
    int Lnum=v[L[nid]]-v[L[lid]];
    if (Lnum>=k)
        return query(L[lid],L[nid],l,mid,k);
    else
        return query(R[lid],R[nid],mid+1,r,k-Lnum);
}
int main(){
    int m,n;
    scanf("%d%d",&m,&n);
    for(int i=1;i<=m;++i)
        scanf("%d",&a[i]),b[i]=a[i];
    sort(b+1,b+m+1);
    int len=unique(b+1,b+m+1)-(b+1);
    for(int i=1;i<=len;++i)
        rev[b[i]]=i;
    root[0]=build(1,len);
    int p=0;
    for(int i=1;i<=n;++i){
        int ca;
        scanf("%d",&ca);
        while(p
  1. P3919:可持久化数组

构造:叶子节点val表示对应下标的值为val,其他节点没用。

功能:在历史版本基础上单点修改,单点查询某一版本的值。

#include
using namespace std;
#define mid (l+r)/2
const int M=3.2e7+10,Mm=1e6+10;
int cnt,root[Mm],a[Mm];
struct Tree{
    int l,r,val;
    Tree(int a=0,int b=0,int c=0):l(a),r(b),val(c){}
}v[M];
int build(int l,int r){
    int rt=++cnt;
    if (l==r){
        v[rt].val=a[l];
        return rt;
    }
    v[rt].l=build(l,mid);
    v[rt].r=build(mid+1,r);
    return rt;
}
int operate(int idp,int l,int r,int pos,int x){
    int rt=++cnt;
    v[rt]=v[idp];
    if (l==r){
        v[rt].val=x;
        return rt;
    }
    if (pos<=mid)
        v[rt].l=operate(v[idp].l,l,mid,pos,x);
    else
        v[rt].r=operate(v[idp].r,mid+1,r,pos,x);
    return rt;
}
int query(int idp,int l,int r,int pos){
    if(l==r)
        return v[idp].val;
    if(pos<=mid)
        return query(v[idp].l,l,mid,pos);
    else
        return query(v[idp].r,mid+1,r,pos);
}
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
        scanf("%d",&a[i]);
    root[0]=build(1,n);
    int ver=0;
    for(int i=1;i<=m;++i){
        int num,op;
        scanf("%d%d",&num,&op);
        if (op==1){
            int a,b;
            scanf("%d%d",&a,&b);
            root[++ver]=operate(root[num],1,n,a,b);
        }
        else{
            int a;
            scanf("%d",&a);
            printf("%d\n",query(root[num],1,n,a));
            root[++ver]=root[num];
        }
    }
    return 0;
}
  1. P3835:可持久化平衡树。对各个以往历史版本进行,插入,删除(可能不存在),查询x排名(可能不存在),查询排名为x的数,求x前驱(可能不存在),求x后继(可能不存在)。

构造:正常的值域线段树,节点表示当前区间数的个数。

注意:一定要注意可能不存在的情况!!!目前来看,值域线段树可以做到一下几点

  • 查询值=pos的数有几个
  • 查询值,>=等等的数有几个。
  • 查询排名为k的数是谁。

如果想再维护一个最大值最小值的话应该也不麻烦。

#include
using namespace std;
#define mid ((l+r)>>1)
const int M=5e5+20;
int cnt,len,rk,num;
struct Tree{
    int l,r,cnt;
    Tree(int a=0,int b=0,int c=0):l(a),r(b),cnt(c){}
}v[55*M];
int build(int l,int r){
    int rt=++cnt;
    if (l==r)
        return rt;
    v[rt].l=build(l,mid),v[rt].r=build(mid+1,r);
    return rt;
}
int query_num(int idp,int l,int r,int pos){//查询值为pos的数有几个
    if (l==r)
        return v[idp].cnt;
    if (pos<=mid)
        return query_num(v[idp].l,l,mid,pos);
    else
        return query_num(v[idp].r,mid+1,r,pos);
}
int operate(int idp,int l,int r,int pos,int x){
    int rt=++cnt;
    v[rt]=v[idp],v[rt].cnt+=x;
    if (l==r)
        return rt;
    if (pos<=mid)
        v[rt].l=operate(v[idp].l,l,mid,pos,x);
    else
        v[rt].r=operate(v[idp].r,mid+1,r,pos,x);
    return rt;
}
int query_rk(int idp,int l,int r,int pos){//查询比pos小的数有几个
    if (l==r)
        return 0;
    if (pos<=mid)
        return query_rk(v[idp].l,l,mid,pos);
    else
        return query_rk(v[idp].r,mid+1,r,pos)+v[v[idp].l].cnt;
}
int query_sa(int idp,int l,int r,int k){//查询排名为k的数
    int &Lnum=v[v[idp].l].cnt;
    if (l==r)
        return l;
    if (Lnum>=k)
        return query_sa(v[idp].l,l,mid,k);
    else
        return query_sa(v[idp].r,mid+1,r,k-Lnum);
}
struct Opt{
    int ver,op,x;
    Opt(int a=0,int b=0,int c=0):ver(a),op(b),x(c){}
}opt[M];
int lsh[M],root[M];
unordered_map rev;
int main(){
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
        scanf("%d%d%d",&opt[i].ver,&opt[i].op,&opt[i].x),lsh[i]=opt[i].x;
    sort(lsh+1,lsh+n+1);
    len=unique(lsh+1,lsh+n+1)-(lsh+1);
    for(int i=1;i<=len;++i)
        rev[lsh[i]]=i;
    root[0]=build(1,len);
    for(int i=1;i<=n;++i)
        if (opt[i].op==1)
            root[i]=operate(root[opt[i].ver],1,len,rev[opt[i].x],1);
        else if (opt[i].op==2){
            if (query_num(root[opt[i].ver],1,len,rev[opt[i].x]))
                root[i]=operate(root[opt[i].ver],1,len,rev[opt[i].x],-1);
            else
                root[i]=root[opt[i].ver];
        }
        else if (opt[i].op==3){
            root[i]=root[opt[i].ver];
            printf("%d\n",query_rk(root[opt[i].ver],1,len,rev[opt[i].x])+1);
        }
        else if (opt[i].op==4){
            root[i]=root[opt[i].ver];
            printf("%d\n",lsh[query_sa(root[opt[i].ver],1,len,opt[i].x)]);
        }
        else if (opt[i].op==5){
            root[i]=root[opt[i].ver];
            rk=query_rk(root[opt[i].ver],1,len,rev[opt[i].x]);
            if (rk==0)
                printf("-2147483647\n");
            else
                printf("%d\n",lsh[query_sa(root[opt[i].ver],1,len,rk)]);
        }
        else if (opt[i].op==6){
            root[i]=root[opt[i].ver];
            rk=query_rk(root[opt[i].ver],1,len,rev[opt[i].x]);
            num=query_num(root[opt[i].ver],1,len,rev[opt[i].x]);
            if (rk==v[root[opt[i].ver]].cnt||(rk==v[root[opt[i].ver]].cnt-1&&num))
                printf("2147483647\n");
            else if (num)
                printf("%d\n",lsh[query_sa(root[opt[i].ver],1,len,rk+2)]);
            else
                printf("%d\n",lsh[query_sa(root[opt[i].ver],1,len,rk+1)]);
        }
    return 0;
}

动态开点线段树

  1. P1908:求逆序对个数

思路:访问之前先看有没有,如果没有就加上,询问的时候如果没有就直接0。

注意

  • cnt初始化必须是1,不然当场爆炸。
  • M能开多大就开多大。
#include
using namespace std;
const int M=1e7+10;
#define mid ((l+r)>>1)
struct Tree{
    int l,r,val;
    Tree(int a=0,int b=0,int c=0):l(a),r(b),val(c){}
}v[M];
int cnt=1;//千万别忘了!!!
void operate(int &id,int l,int r,int pos,int x){
    if (!id)//
        id=++cnt;//
    v[id].val+=x;
    if (l==r)
        return;
    if (pos<=mid)
        operate(v[id].l,l,mid,pos,x);
    else
        operate(v[id].r,mid+1,r,pos,x);
}
int query(int id,int l,int r,int ql,int qr){
    if (!id)//
        return 0;//
    if (ql<=l&&r<=qr)
        return v[id].val;
    int sum=0;
    if (ql<=mid)
        sum+=query(v[id].l,l,mid,ql,qr);
    if (mid
  1. CF915E:待补,据说是动态开点线段树,lazy标记处理如下:(很抱歉忘了是哪个博主的博客了……如有侵权会立刻删除)
inline void pushdown(int now,int l,int r)
{
    if(lazy[now]==-1)   return;
    int k=lazy[now],m=(l+r)>>1;
 
    if(!lson[now]) lson[now]=++tot;
    sum[lson[now]]=k*(m-l+1);
    lazy[lson[now]]=lazy[now];
    
    if(!rson[now]) rson[now]=++tot;
    sum[rson[now]]=k*(r-(m+1)+1);
    lazy[rson[now]]=lazy[now];
    
    lazy[now]=-1;
}

并查集

  1. P3367:合并+询问是否在同一集合。

路径压缩很容易理解,这里再用一下按秩合并,后面可持久化并查集需要用。记录每个并查集的大小(或者说是并查集关系树的树高(最下面的节点是1,表示这个节点的儿子找到他需要经过的最长步数,或者说是最深的儿子)。然后其实也比较简单据说如果两个都用的话可以达到线性。

#include
using namespace std;
const int M=2e5+10;
int fa[M],rk[M];
int find(int x){
    return fa[x]==x?x:find(fa[x]);//return fa[x]==x?x:fa[x]=find(fa[x]);这是换成路径压缩
}
void merge(int x,int y){
    x=find(x),y=find(y);
    if (x!=y)
        if (rk[x]<=rk[y])
            fa[x]=y,rk[y]=max(rk[y],rk[x]+1);
        else
            fa[y]=x,rk[x]=max(rk[x],rk[y]+1);

}
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
        fa[i]=i,rk[i]=1;
    for(int i=1;i<=m;++i){
        int op,x,y;
        scanf("%d%d%d",&op,&x,&y);
        if (op==1)
            merge(x,y);
        else
            printf("%c\n",find(x)==find(y)?'Y':'N');
    }
    return 0;
}
  1. P3402:可持久化并查集

思路基本一样,就是把fa和rk树上放在可持久化数组上搞,这样每次修改或者询问数组上的值就是\(O(\log n)\)的,所以不能用路径压缩(因为每次find的都是都要进行一大堆修改操作,完全可以构造数据卡掉)。

看一些题解,感觉部分算法不能可持久化的原因是时间复杂度是均摊的。如果再加上一个log,那么均摊之后很有可能就不是原先的复杂度了。

总之这个模板还是非常有用的(感觉)。

#include
using namespace std;
#define mid ((l+r)>>1)
const int M=2e5+20;
int cnt,root[M],n;
struct Tree{
    int l,r,fa,rk;
    Tree(int a=0,int b=0,int c=0,int d=0):l(a),r(b),fa(c),rk(d){}
}v[36*M];
int build(int l,int r){
    int rt=++cnt;
    if (l==r){
        v[rt].fa=l,v[rt].rk=1;
        return rt;
    }
    v[rt].l=build(l,mid),v[rt].r=build(mid+1,r);
    return rt;
}
int operate_rk(int idp,int l,int r,int pos,int x){
    int rt=++cnt;
    v[rt]=v[idp];
    if (l==r){
        v[rt].rk=x;
        return rt;
    }
    if (pos<=mid)
        v[rt].l=operate_rk(v[idp].l,l,mid,pos,x);
    else
        v[rt].r=operate_rk(v[idp].r,mid+1,r,pos,x);
    return rt;
}
int operate_fa(int idp,int l,int r,int pos,int x){
    int rt=++cnt;
    v[rt]=v[idp];
    if (l==r){
        v[rt].fa=x;
        return rt;
    }
    if (pos<=mid)
        v[rt].l=operate_fa(v[idp].l,l,mid,pos,x);
    else
        v[rt].r=operate_fa(v[idp].r,mid+1,r,pos,x);
    return rt;
}
Tree query(int idp,int l,int r,int pos){
    if(l==r)
        return v[idp];
    if(pos<=mid)
        return query(v[idp].l,l,mid,pos);
    else
        return query(v[idp].r,mid+1,r,pos);
}
int find(int idp,int x){
    int fax=query(idp,1,n,x).fa;
    return fax==x?x:find(idp,fax);//return fa[x]==x?x:fa[x]=find(fa[x]);这是换成路径压缩
}
int merge(int idp,int x,int y){
    x=find(idp,x),y=find(idp,y);
    if (x!=y){
        int rkx=query(idp,1,n,x).rk,rky=query(idp,1,n,y).rk;
        if (rkx<=rky)
            return operate_rk(operate_fa(idp,1,n,x,y),1,n,y,max(rky,rkx+1));
        else
            return operate_rk(operate_fa(idp,1,n,y,x),1,n,x,max(rkx,rky+1));
    }
    return idp;
}
int main(){
    int m;
    scanf("%d%d",&n,&m);
    root[0]=build(1,n);
    int now=0;
    for(int i=1;i<=m;++i){
        int op;
        scanf("%d",&op);
        if (op==2){
            int c;
            scanf("%d",&c);
            root[now+1]=root[c],++now;
        }
        else if (op==1){
            int a,b;
            scanf("%d%d",&a,&b);
            root[now+1]=merge(root[now],a,b),++now;
        }
        else{
            int a,b;
            scanf("%d%d",&a,&b);
            printf("%d\n",find(root[now],a)==find(root[now],b)?1:0);
            root[now+1]=root[now],++now;
        }
    }
    return 0;
}

总结

今天写了好多题啊,主要整了一下可持久化的数据结构,现在还剩带修的主席树(虽然和主席树无关)和二逼平衡树没有搞。

感觉自己最近训练特别菜的原因,是自己一直在做一些模板题,真正有提升的部分应该是和qz爷一样,刷难题,动脑子,而不是天天只在学习而不去练习,这样虽然练了手,但是脑子却一直锈着,自然面对思维乱搞题直接起飞。

但是之前的比赛也给了我们经验,码力不够的话,连签到题都不会做。就是这样。

所以还是要从基础连起,就是签到题。多写,才知道怎么搞数据结构,代码具体是怎么实现的,原理是什么,以及什么地方可以做修改。

只不过,对我来说,如果想冲一冲EC,时间是真的不太够了呢。

明年继续吧,只能这么说了,眼光放到明年的话,那么现在的一切都还是非常有意义的。

1年,我就不信上不了红?试试看。

数据结构部分感想

其实数据结构主要考察的是一些思想。

比如可持久化数据结构,其实本质都是复用已有信息,本质上和线段树也没什么区别,写成结构体的话,都是l,r,val。只不过l,r不一样了。

再有,只要涉及区间加减的操作,那么就可以考虑差分,变成单点的操作,最后树状数组再合并。这一点17ECFinal那个J题。不说了,真实太思博了(指了指自己)。

千万不能像之前那样什么东西只能用一个去做,其实看了那么多题解,真的不是这样的,主要靠思考,而不是套用(比如主席树——区间第k小)之类的。实际上发现,值域线段树可以代替平衡树的基本操作,但有些地方就不行了,比如说文艺平衡树,好像就必须得用那些传统的平衡树结构来操作了。同样,带修主席树根本就不是可持久化的数据结构,只不过因为树套树肯定会MLE,所以动态开点。本质上其实就是一种新思路,只不过在不带修改的时候,时间复杂度不够好,有更加优秀的主席树。对于带修的问题,根本没法用主席树,所以就只能树套树了。这样看的话,其实香港H题就是个人太菜了……如果之前做过这些题的话,H题就纯一裸题。

明日计划

  1. 动态开点树状数组——>带修主席树
  2. 替罪羊树
  3. 三维扫描线
  4. CDQ代替树状数组做单点修改+区间求和

你可能感兴趣的:(【日记】12.9)