题目大意:
给定T组数据,n个数,m次询问。每次询问[L,R]区间内,比k小于等于k的数的个数。
解题思路:
权值线段树可以在[1,n]区间内做Rank操作,但是题目要求在[L,R]内做Rank操作。我们可以想到主席树这种数据结构。解决这道题需要三个函数。
- build函数
在一开始的时候要建立一棵空的主席树,即所有叶子节点信息都是0的主席树。叶子节点的值一定要有,但是它的ls,rs可以不给出。因为我们查询到叶子节点的时候,就会进入if(l==r)的条件判断中。 - update函数
在本题中,我们每次只需要单点更新即可。所以,我们可以选择从上往下和从下往上(pushup)两种更新方法。 - query函数
我们可以同时询问rt[l-1]和rt[r]两棵子树,并利用前缀和的思想,处理[l,r]区间的询问。
代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+100;
int tot,rt[maxn];//主席树
struct node{int sum,ls,rs;}t[maxn<<4];
int a[maxn],b[maxn],len,n,m;//离散化
int getid(int val) {return lower_bound(b+1,b+len+1,val)-b;}
int build(int l,int r){
int now = ++tot; //动态开节点
if(l==r){
t[tot].sum = 0;
return now;
}
int mid = l+r>>1;
t[now].ls = build(l,mid);
t[now].rs = build(mid+1,r);
return now;
}
int update(int rt,int l,int r,int pos){
int now = ++tot;
t[now] = t[rt] , t[now].sum++; //从上向下更新
if(l==r){
return now;
}
int mid = l+r>>1;
//根据pos的位置,选择更新方向
if(pos<=mid) t[now].ls = update(t[rt].ls,l,mid,pos);
else t[now].rs = update(t[rt].rs,mid+1,r,pos);
return now;
}
int query(int u,int v,int l,int r,int k){//求出小于等于k的信息
//利用前缀和思想,t[u].sum-t[v].sum就是区间[l,r]之间包含的信息
int mid = l+r>>1,p = t[t[u].ls].sum-t[t[v].ls].sum;
if(l==r) return t[u].sum-t[v].sum;
if(k<=mid) return query(t[u].ls,t[v].ls,l,mid,k);
else return p+query(t[u].rs,t[v].rs,mid+1,r,k);
}
int main(){
int _,cnt=0;
scanf("%d",&_);
while(_--){
printf("Case %d:\n",++cnt);
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);
len = unique(b+1,b+n+1)-b-1;//离散化
rt[0] = build(1,len); //建立空树
for(int i=1;i<=n;i++) //每[1,i]区间都新开一棵主席树
rt[i] = update(rt[i-1],1,len,getid(a[i]));
while(m--){
int l,r,k;
scanf("%d%d%d",&l,&r,&k);
l++,r++;
int h = upper_bound(b+1,b+len+1,k)-b-1;
// 对于1 2 3 5 6
//查询4时,使用upper_bound会是3,使用lower_bound也是3
//查询3时,使用upper_bound会是3,使用lower_bound会是2
//所以使用lower_bound时,要注意特判即可
if(h) printf("%d\n",query(rt[r],rt[l-1],1,len,h));
else printf("0\n"); //这里WA了好多次
}
}
return 0;
}