这题是求一段区间中小于等于P的数的个数。。
这题可以用离线线段树写
将所有的询问离线读入之后,按H从小到大排序。然后对于所有的结点也按从小到大排序,然后根据查询的H,将比H小的点加入到线段树,然后就是一个区间和。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #define MAXN 100050 #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 using namespace std; int sum[MAXN<<2]; int ans[MAXN]; int N,M; struct node{ int pos,val; bool operator <(const node &b)const{ return val<b.val; } }nn[MAXN]; struct eee{ int l,r,h,pos; bool operator <(const eee &b)const{ return h<b.h; } }ee[MAXN]; void pushup(int rt){ sum[rt]=sum[rt<<1]+sum[rt<<1|1]; } void build(int l,int r,int rt){ if(l==r){ sum[rt]=0; return; } int m=(l+r)>>1; build(lson); build(rson); pushup(rt); } void update(int p,int val,int l,int r,int rt){ if(l==r&&l==p){ sum[rt]+=val; return; } int m=(l+r)>>1; if(p<=m)update(p,val,lson); else update(p,val,rson); pushup(rt); } int query(int L,int R,int l,int r,int rt){ if(L<=l&&R>=r){ return sum[rt]; } int m=(l+r)>>1; int re=0; if(L<=m)re+=query(L,R,lson); if(R>m)re+=query(L,R,rson); return re; } int main(){ int tt; scanf("%d",&tt); for(int cas=1;cas<=tt;cas++){ memset(ans,0,sizeof(ans)); scanf("%d%d",&N,&M); for(int i=0;i<N;i++){ scanf("%d",&nn[i].val); nn[i].pos=i+1; } for(int i=0;i<M;i++){ scanf("%d%d%d",&ee[i].l,&ee[i].r,&ee[i].h); ee[i].l++,ee[i].r++; ee[i].pos=i; } sort(nn,nn+N); sort(ee,ee+M); build(1,N,1); int k=0; for(int i=0;i<M;i++){ while(k<N&&nn[k].val<=ee[i].h){ update(nn[k].pos,1,1,N,1); k++; } ans[ee[i].pos]=query(ee[i].l,ee[i].r,1,N,1); } printf("Case %d:\n",cas); for(int i=0;i<M;i++) printf("%d\n",ans[i]); } return 0; }
题解是二分划分树求第K小值与P比较,然后就可以得到小于等于P的数的个数
http://acm.hdu.edu.cn/showproblem.php?pid=4417
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #define MAXN 100050 #define lson l,mid,rt<<1 #define rson mid+1,r,rt<<1|1 using namespace std; int sorted[MAXN]; int toLeft[20][MAXN];//20代表的是20层 int val[20][MAXN]; void build(int d,int l,int r,int rt){ if(l==r)return; int mid=(l+r)>>1; int lsame=mid-l+1;//lsame表示和val_mid相等且分到左边的 for(int i=l;i<=r;i++){ if(val[d][i]<sorted[mid]){ lsame--;//先假设左边的数(mid - l + 1)个都等于val_mid,然后把实际上小于val_mid的减去 } } int lpos=l; int rpos=mid+1; int same=0; for(int i=l;i<=r;i++){ if(i==l){ toLeft[d][i]=0;//toLeft[i]表示[ l , i ]区域里有多少个数分到左边 } else{ toLeft[d][i]=toLeft[d][i-1]; } if(val[d][i]<sorted[mid]){ toLeft[d][i]++; val[d+1][lpos++]=val[d][i]; } else if(val[d][i]>sorted[mid]){ val[d+1][rpos++]=val[d][i]; } else{ if(same<lsame){//有lsame的数是分到左边的 same++; toLeft[d][i]++; val[d+1][lpos++]=val[d][i]; } else{ val[d+1][rpos++]=val[d][i]; } } } build(d+1,lson); build(d+1,rson); } int query(int L,int R,int k,int d,int l,int r,int rt){ if(l==r){ return val[d][l]; } int s;//s表示[ L , R ]有多少个分到左边 int ss;//ss表示 [l , L-1 ]有多少个分到左边 int mid=(l+r)>>1; if(L==l){ s=toLeft[d][R]; ss=0; } else{ s=toLeft[d][R]-toLeft[d][L-1]; ss=toLeft[d][L-1]; } if(s>=k){//有多于k个分到左边,显然去左儿子区间找第k个 int newl=l+ss; int newr=l+ss+s-1;//计算出新的映射区间 return query(newl,newr,k,d+1,lson); } else { int bb=L-l-ss;//bb表示 [l , L-1 ]有多少个分到右边 int b=R-L-s+1;//b表示 [L , R]有多少个分到右边 int newl=mid+bb+1; int newr=mid+bb+b; return query(newl,newr,k-s,d+1,rson); } } int main() { freopen("in.txt","r",stdin); int cas=1; int T; scanf("%d",&T); while(T--){ printf("Case %d:\n",cas++); int n,m; scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){ scanf("%d",&val[0][i]); sorted[i]=val[0][i]; } sort(sorted+1,sorted+n+1); build(0,1,n,1); while(m--){ int l,r,p; scanf("%d%d%d",&l,&r,&p); //因为这个划分数模版的下标是1-n,而这题是0~n-1,故需要将l++,r++ l++,r++; int ll=1,rr=(r-l+1); int a; while(ll<=rr){ int mid=ll+((rr-ll)>>1); a=query(l,r,mid,0,1,n,1); if(a>p)rr=mid-1; else ll=mid+1; } printf("%d\n",rr); } } return 0; }