• 【日记】12.9


    12.9日记

    对顶堆

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

    1. P1801:插入+输出第k大。
    #include<bits/stdc++.h>
    using namespace std;
    const int M=2e5+20;
    int a[M];
    priority_queue<int> qb;
    priority_queue<int,vector<int>,greater<int> > qs;
    inline void operate(int num){
        while(qb.size()<num)
            qb.push(qs.top()),qs.pop();
        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<ca)
                insert(a[++p]);
            operate(q);
            printf("%d
    ",qb.top());
        }
        return 0;
    }
    

    主席树

    1. P1801:

    拿大炮打苍蝇……

    #include<bits/stdc++.h>
    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<int,int> 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<ca)
                root[p+1]=operate(root[p],1,len,rev[a[p+1]],1),++p;
            printf("%d
    ",b[query(root[0],root[ca],1,len,i)]);
        }
        return 0;
    }
    
    1. P3919:可持久化数组

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

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

    #include<bits/stdc++.h>
    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
    ",query(root[num],1,n,a));
    			root[++ver]=root[num];
    		}
    	}
    	return 0;
    }
    
    1. P3835:可持久化平衡树。对各个以往历史版本进行,插入,删除(可能不存在),查询x排名(可能不存在),查询排名为x的数,求x前驱(可能不存在),求x后继(可能不存在)。

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

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

    • 查询值=pos的数有几个
    • 查询值<pos的数有几个。以上两个结合可以知道各种<=,<,>,>=等等的数有几个。
    • 查询排名为k的数是谁。

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

    #include<bits/stdc++.h>
    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<int,int> 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
    ",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
    ",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
    ");
    			else
    				printf("%d
    ",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
    ");
    			else if (num)
    				printf("%d
    ",lsh[query_sa(root[opt[i].ver],1,len,rk+2)]);
    			else
    				printf("%d
    ",lsh[query_sa(root[opt[i].ver],1,len,rk+1)]);
    		}
    	return 0;
    }
    

    动态开点线段树

    1. P1908:求逆序对个数

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

    注意

    • cnt初始化必须是1,不然当场爆炸。
    • M能开多大就开多大。
    #include<bits/stdc++.h>
    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<qr)
    		sum+=query(v[id].r,mid+1,r,ql,qr);
    	return sum;
    }
    int main(){
    	int n,root=1;
    	scanf("%d",&n);
    	long long ans=0;
    	for(int i=1;i<=n;++i){
    		int c;
    		scanf("%d",&c);
    		operate(root,1,1e9,c,1);
    		ans+=query(1,1,1e9,c+1,1e9);
    	}
    	printf("%lld
    ",ans);
    	return 0;
    }
    
    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<bits/stdc++.h>
    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
    ",find(x)==find(y)?'Y':'N');
    	}
    	return 0;
    }
    
    1. P3402:可持久化并查集

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

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

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

    #include<bits/stdc++.h>
    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
    ",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代替树状数组做单点修改+区间求和
  • 相关阅读:
    implement a plus b / a minus b without using any arithmetic operators
    The month's days(leap year defination)
    sort algorithm
    js数组去重
    rabbitmq环境安装
    线程池
    课外加餐:4 | 页面性能工具:如何使用 Performance?
    课外加餐:5 | 性能分析工具:如何分析Performance中的Main指标?
    结束语
    课外加餐:6 | HTTPS:浏览器如何验证数字证书?
  • 原文地址:https://www.cnblogs.com/diorvh/p/12014406.html
Copyright © 2020-2023  润新知