题目链接:hdu 4417 Super Mario
Sample Input
1
10 10
0 5 2 7 5 4 3 8 7 7
2 8 6
3 5 0
1 3 1
1 9 4
0 1 0
3 5 5
5 5 1
4 6 3
1 5 7
5 7 3
Sample Output
Case 1:
4
0
0
3
1
2
0
1
5
1
题意:给一个数组,求区间中小于等于h的数字个数
思路:裸的主席树,第一次用大致说一下自己的理解
主席树就是很多的线段树,当一个点更新的时候(上图的5更新),影响的是一条链(1-2-5),主席树保留了不需要更新的结点,第i棵线段树维护1~i的区间信息
这一题中,0 5 2 7 5 4 3 8 7 7,离散化后得 0 2 3 4 5 7 8对应下标1 2 3 4 5 6 7,在insert操作结束之后会得到下图所示的树,圆圈中的数字表示这个区间中的数字个数,存储在size[i]
要记住size存储的是数字个数,叶子结点是排序后离散化的下标,即权值,其他的写法的改变,按照线段树的写法来即可
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
#define N 100005
int root[N];//root[i]表示第i课线段树
int size[N*25],lchild[N*25],rchild[N*25];
int tot;
void insert(int last,int cur,int L,int R,int k) //单点更新
{
size[cur]=size[last]+1;//将前一个树的信息复制过来
lchild[cur]=lchild[last];
rchild[cur]=rchild[last];
if(L==R)return ;
int mid=L+R>>1;
if(k<=mid) insert(lchild[last],lchild[cur]=++tot,L,mid,k);//对于需要更改的节点都需要新增节点
else insert(rchild[last],rchild[cur]=++tot,mid+1,R,k);
}
int query(int last,int cur,int l,int r,int L,int R)
{
if(l>r)return 0;
if(L==l&&r==R){
return size[cur]-size[last];
}
int mid=(L+R)>>1;
if(l>mid)return query(rchild[last],rchild[cur],l,r,mid+1,R);
else if(r<=mid)return query(lchild[last],lchild[cur],l,r,L,mid);
else return query(rchild[last],rchild[cur],mid+1,r,mid+1,R)+query(lchild[last],lchild[cur],l,mid,L,mid);
}
int a[100005];
int b[100005];
int main(){
int t;
scanf("%d",&t);
int c=1;
while(t--){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
b[i]=a[i];
}
sort(b+1,b+n+1);//排序
int nn=unique(b+1,b+n+1)-b-1;//去重
//上面是在离散化处理
tot=0;
for(int i=1;i<=n;i++){
int k=lower_bound(b+1,b+nn+1,a[i])-b;//找到a[i]在整个数列中是第几大
insert(root[i-1],root[i]=++tot,1,nn,k);
}
printf("Case %d:\n",c++);
for(int i=1;i<=m;i++){
int l,r,h;
scanf("%d%d%d",&l,&r,&h);
l++;r++;
int k=upper_bound(b+1,b+1+nn,h)-b-1;//找到h在整个数列中是第几大
printf("%d\n",query(root[l-1],root[r],1,k,1,nn));
}
}
return 0;
}