您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:
1.查询k在区间内的排名
2.查询区间内排名为k的值
3.修改某一位值上的数值
4.查询k在区间内的前驱(前驱定义为小于x,且最大的数)
5.查询k在区间内的后继(后继定义为大于x,且最小的数)
您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:
1.查询k在区间内的排名
2.查询区间内排名为k的值
3.修改某一位值上的数值
4.查询k在区间内的前驱(前驱定义为小于x,且最大的数)
5.查询k在区间内的后继(后继定义为大于x,且最小的数)
第一行两个数 n,m 表示长度为n的有序序列和m个操作
第二行有n个数,表示有序序列
下面有m行,opt表示操作标号
若opt=1 则为操作1,之后有三个数l,r,k 表示查询k在区间[l,r]的排名
若opt=2 则为操作2,之后有三个数l,r,k 表示查询区间[l,r]内排名为k的数
若opt=3 则为操作3,之后有两个数pos,k 表示将pos位置的数修改为k
若opt=4 则为操作4,之后有三个数l,r,k 表示查询区间[l,r]内k的前驱
若opt=5 则为操作5,之后有三个数l,r,k 表示查询区间[l,r]内k的后继
对于操作1,2,4,5各输出一行,表示查询结果
1.n和m的数据范围:n,m<=50000
2.序列中每个数的数据范围:[0,1e8]
树套树第一题。位置线段树套权值平衡树(treap)。
以位置为关键字建一棵线段树,在线段树的每个节点上再建一棵以权值为关键字的treap即可。
五个操作的实现方法:
1.查询k在区间内排名(Getrank)
首先在线段树中找到区间的位置(Getrank),然后在找到的每一小段区间中的treap上找比k大的数的个数(Askrank),输出个数+1即可
2.查询区间内排名为k的值(Getnum)
首先二分答案(Getnum),然后用操作1中的Getrank找到目前二分出来的数在区间的排名,如果排名<=k,则在右半部分继续找,否则在左半部分
3.修改某一位置上的数值(Modify)
这个操作等价于删除一个数,再插入一个数。但是要在线段树中每一个包含此数的区间都执行这两个操作。
4.查询前驱(Getpre)
在线段树中找到区间的位置,在每一个区间中找小于k的最大数,最后取最大值即可
5.查询后继(Getnext)
在线段树中找到区间的位置,在每一个区间中找小于k的最小数,最后取最小值即可
注意:
1.对于重复的数值的处理方法是在treap上维护一个weight,表示与当前结点的权值相同的数有几个。
2.查询操作要给全出口,否则就会死循环。。
#include <iostream> #include <algorithm> #include <cstring> #include <cstdio> #include <cstdlib> #define N 3000005 using namespace std; struct treap { int fix,weight,size,l,r,data; }a[N]; int ans,num,root[N],tot=0,x[50005],n,m; void Push_up(int x) { a[x].size=a[a[x].r].size+a[a[x].l].size+a[x].weight; } void zag(int &x) { int o=a[x].l; a[x].l=a[o].r; a[o].r=x; a[o].size=a[x].size; Push_up(x); x=o; } void zig(int &x) { int o=a[x].r; a[x].r=a[o].l; a[o].l=x; a[o].size=a[x].size; Push_up(x); x=o; } void New_node(int &x,int data) { x=++tot; a[x].weight=a[x].size=1; a[x].data=data; a[x].fix=rand(); a[x].l=a[x].r=0; } void Insert(int &x,int data) { if (!x) { New_node(x,data); return; } a[x].size++; if (a[x].data==data) { a[x].weight++; return; } if (data<a[x].data) { Insert(a[x].l,data); if (a[a[x].l].fix<a[x].fix) zag(x); } else { Insert(a[x].r,data); if (a[a[x].r].fix<a[x].fix) zig(x); } } void Delet(int &x,int data) { if (a[x].data==data) { if (a[x].weight>1) //注意 { a[x].weight--; a[x].size--; return; } if (a[x].l*a[x].r==0) x=a[x].l+a[x].r; else if (a[a[x].l].fix<a[a[x].r].fix) { zag(x); Delet(x,data); //这里为什么不直接Delet(a[x].r,data)?因为删掉这个结点后a[x].size就会出错 } else { zig(x); Delet(x,data); } return; } a[x].size--; if (a[x].data>data) Delet(a[x].l,data); else Delet(a[x].r,data); } void Build(int x,int l,int r,int now,int data) { Insert(root[x],data); if (l==r) return; int m=(l+r)>>1; if (now<=m) Build(x<<1,l,m,now,data); else Build(x<<1|1,m+1,r,now,data); } void Askrank(int x,int data) { if (!x) return; if (a[x].data==data) { ans+=a[a[x].l].size; return; } if (a[x].data>data) { Askrank(a[x].l,data); return; } ans=ans+a[a[x].l].size+a[x].weight; Askrank(a[x].r,data); } void Getrank(int x,int lx,int rx,int l,int r,int data) { if (lx>=l&&rx<=r) { Askrank(root[x],data); return; } if (lx==rx) return; int m=(lx+rx)>>1; if (l<=m) Getrank(x<<1,lx,m,l,r,data); if (r>m) Getrank(x<<1|1,m+1,rx,l,r,data); } void Getnum(int l,int r,int k) { int lx=0,rx=100000000; while (lx<=rx) { int m=(lx+rx)>>1; ans=1; Getrank(1,1,n,l,r,m); if (ans<=k) { lx=m+1; num=m; } else rx=m-1; } } void Modify(int x,int l,int r,int p,int data,int c) { Delet(root[x],data); Insert(root[x],c); if (l==r) return; int m=(l+r)>>1; if (p<=m) Modify(x<<1,l,m,p,data,c); else Modify(x<<1|1,m+1,r,p,data,c); } void Askpre(int x,int data) { if (!x) return; if (a[x].data>=data) Askpre(a[x].l,data); //不需要Askpre(a[x].r,data),因为前驱是最小的那个数 else { if (ans<a[x].data) ans=a[x].data; Askpre(a[x].r,data); } } void Getpre(int x,int lx,int rx,int l,int r,int data) { if (lx>=l&&rx<=r) { Askpre(root[x],data); return; } if (lx==rx) return; int m=(lx+rx)>>1; if (l<=m) Getpre(x<<1,lx,m,l,r,data); if (r>m) Getpre(x<<1|1,m+1,rx,l,r,data); } void Asknext(int x,int data) { if (!x) return; if (a[x].data>data) { if (a[x].data<ans) ans=a[x].data; Asknext(a[x].l,data); } else Asknext(a[x].r,data); } void Getnext(int x,int lx,int rx,int l,int r,int data) { if (lx>=l&&rx<=r) { Asknext(root[x],data); return; } if (lx==rx) return; int m=(lx+rx)>>1; if (l<=m) Getnext(x<<1,lx,m,l,r,data); if (r>m) Getnext(x<<1|1,m+1,rx,l,r,data); } void Print(int x) { printf("%d\n",x); } int main() { scanf("%d%d",&n,&m); for (int i=1;i<=n;i++) scanf("%d",&x[i]); for (int i=1;i<=n;i++) Build(1,1,n,i,x[i]); while (m--) { int q,l,r,k,p; scanf("%d",&q); switch(q) { case 1: scanf("%d%d%d",&l,&r,&k); ans=1; Getrank(1,1,n,l,r,k); Print(ans); break; //记得写break case 2: scanf("%d%d%d",&l,&r,&k); Getnum(l,r,k); Print(num); break; case 3: scanf("%d%d",&l,&k); p=x[l]; x[l]=k; Modify(1,1,n,l,p,k); break; case 4: scanf("%d%d%d",&l,&r,&k); ans=0; Getpre(1,1,n,l,r,k); Print(ans); break; case 5: scanf("%d%d%d",&l,&r,&k); ans=100000000; Getnext(1,1,n,l,r,k); Print(ans); break; } } return 0; }
感悟:
有关平衡树的题特别容易写挂的原因就是操作和结点维护值的名字相似(zig,zag),常常一字之差是TLE和AC的差距。。所以做题要细心!
原文地址:http://blog.csdn.net/regina8023/article/details/41642487