传送门:SDUT 2610
题目大意:
给你一个数组 P 和 m 组询问,让你求在区间 [ l , r ] 中满足 A<= Pi <=B 的数有多少个。
前置技能:
2.划分树原理及应用。
思路:
主席树和划分树都可以解决区间第 K 大值的求解问题,但本题是求解区间内在某个范围内的数的个数。该怎么办呢?
我们可以换个思路,求出区间内第一个出现的 A 排第几大,最后一个出现的 B 排第几大,然后相减就可以得到答案了。这里可以用二分的方法每次求解区间第 mid 大的,然后和 A 、B 比较大小,以确定为第几大。
需要说明一下的是,主席树版的会比划分树版的快 100ms 左右,但是划分树版的更好理解。
代码:
//划分树版 #include<stdio.h> #include<string.h> #include<iostream> #include<algorithm> using namespace std; #define MAXN 50010 int tree[20][MAXN]; //表示每层每个位置的值 int sorted[MAXN]; //已经排好序的数 int toleft[20][MAXN]; //toleft[dep][i]表示第dep层从1到i有数分入左边 void build(int l,int r,int dep) { if(l==r) return; int i,ln,rn; int mid=(l+r)>>1; int same=mid-l+1; //表示等于中间值而且被分入左边的个数 for(i=l;i<=r;i++)//先假设左边的(mid-l+1)个数都等于中间值,然后把实际上小于中间值的减去 if(tree[dep][i]<sorted[mid]) same--; ln=l; //左子树开始的位置 rn=mid+1; //右子树开始的位置 for(i=l;i<=r;i++) { if(tree[dep][i]<sorted[mid]) tree[dep+1][ln++]=tree[dep][i]; else if(tree[dep][i]==sorted[mid]&&same>0) { //等于中位数的same个数放入左子树 tree[dep+1][ln++]=tree[dep][i]; same--; } else tree[dep+1][rn++]=tree[dep][i]; toleft[dep][i]=toleft[dep][l-1]+ln-l; } build(l,mid,dep+1); build(mid+1,r,dep+1); } //查询区间第k小的数,[L,R]为大区间,[l,r]是要查询的小区间 int query(int L,int R,int l,int r,int dep,int k) { if(l==r) return tree[dep][l]; int mid=(L+R)>>1; int cnt=toleft[dep][r]-toleft[dep][l-1];//当前层当前区间划分到左子树的数的个数 int newl,newr; if(cnt>=k) { //如果要查找的数被划分到了左子树,则去左子树查找 newl=L+toleft[dep][l-1]-toleft[dep][L-1]; newr=newl+cnt-1; return query(L,mid,newl,newr,dep+1,k); } else { //否则,去右子树查找 newr=r+toleft[dep][R]-toleft[dep][r]; newl=newr-(r-l-cnt); return query(mid+1,R,newl,newr,dep+1,k-cnt); } } int main() { int i,j,t,n,m,L,R,a,b,cas,tmp,low,high; scanf("%d",&t); cas=1; while(t--) { scanf("%d%d",&n,&m); memset(tree,0,sizeof(tree)); for(i=1;i<=n;i++) { scanf("%d",&tree[0][i]); sorted[i]=tree[0][i]; } sort(sorted+1,sorted+n+1); build(1,n,0); printf("Case #%d:\n",cas++); while(m--) { scanf("%d%d%d%d",&L,&R,&a,&b); int l,r,mid; l=1; r=R-L+2; while(l<r) { //二分查找第一个 a是区间第几大 mid=(l+r)>>1; tmp=query(1,n,L,R,0,mid); if(tmp>=a) r=mid; else l=mid+1; } low=l; l=1; r=R-L+2; while(l<r) { //二分查找最后一个 b是区间第几大 mid=(l+r)>>1; tmp=query(1,n,L,R,0,mid); if(tmp>b) r=mid; else l=mid+1; } high=l; printf("%d\n",high-low); } } return 0; }
//主席树版 #include<stdio.h> #include<string.h> #include<iostream> #include<algorithm> #define MAXN 50010 using namespace std; int tol; //若tol值相同,则L、R、sum就表示同一个节点 //L为左端点的编号,R为右端点的编号,sum表示区间[L,R]内数的个数 int L[MAXN<<5],R[MAXN<<5],sum[MAXN<<5]; int p[MAXN],q[MAXN],T[MAXN]; //T记录每个元素对应的根节点 int build(int l,int r) { //建树,参数表示左右端点 int mid,root=++tol; sum[root]=0; //区间内数的个数为0 if(l<r) { mid=(l+r)>>1; L[root]=build(l,mid); //构造左子树并将左端点编号存入L R[root]=build(mid+1,r); //构造右子树并将右端点编号存入R } return root; } int update(int pre,int l,int r,int pos) {//更新,参数分别为:上一线段树的根节点编号,左右端点,插入数在原数组中排第pos //从根节点往下更新到叶子,新建立出一路更新的节点,这样就是一颗新树了。 int mid,root=++tol; L[root]=L[pre]; //先让其等于前面一颗树 R[root]=R[pre]; //先让其等于前面一颗树 sum[root]=sum[pre]+1; //当前节点一定被修改,数的个数+1 if(l<r) { mid=(l+r)>>1; if(pos<=mid) L[root]=update(L[pre],l,mid,pos); //插入到左子树 else R[root]=update(R[pre],mid+1,r,pos); //插入到右子树 } return root; } int query(int u,int v,int l,int r,int k) { //查询,参数分别为:两颗线段树根节点的编号,左右端点,第k大 //只会查询到相关的节点 int mid,num; if(l==r) return l; mid=(l+r)>>1; num=sum[L[v]]-sum[L[u]]; //当前询问的区间中左子树中的元素个数 //如果左儿子中的个数大于k,则要查询的值在左子树中 if(num>=k) return query(L[u],L[v],l,mid,k); //否则在右子树中 else return query(R[u],R[v],mid+1,r,k-num); } int main() { int i,t,n,m,L,R,a,b; int tmp,num,pos,cas,low,high; scanf("%d",&t); cas=1; while(t--) { scanf("%d%d",&n,&m); for(i=1;i<=n;i++) { scanf("%d",&p[i]); q[i]=p[i]; } sort(q+1,q+n+1); num=unique(q+1,q+n+1)-q-1; //num为不同数的个数 tol=0; //编号初始化 T[0]=build(1,num); //1~num即区间 for(i=1;i<=n;i++) { //实际上是对每个元素建立了一颗线段树,保存其根节点 pos=lower_bound(q+1,q+num+1,p[i])-q; //pos就是当前数在原数组中排第pos T[i]=update(T[i-1],1,num,pos); //在上一棵线段树的基础上修改 } int l,r,mid; printf("Case #%d:\n",cas++); while(m--) { scanf("%d%d%d%d",&L,&R,&a,&b); l=1; r=R-L+2; while(l<r) { //二分查找第一个 a是区间第几大 mid=(l+r)>>1; pos=query(T[L-1],T[R],1,num,mid); if(q[pos]>=a) r=mid; else l=mid+1; } low=l; l=1; r=R-L+2; while(l<r) { //二分查找最后一个 b是区间第几大 mid=(l+r)>>1; pos=query(T[L-1],T[R],1,num,mid); if(q[pos]>b) r=mid; else l=mid+1; } high=l; printf("%d\n",high-low); } } return 0; }