传送门: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;
}