hdu4417 Super Mario (树状数组/分块/主席树)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4417
题目大意:给定一个长度为n的序列,有m个询问,每次询问包含l,r,h,即询问区间[l,r]小于等于h的数目。
解题思路:很多种解法,写了三种
1.树状数组离线处理
将序列和所有操作的h从小到大排序,都设为结构体类型以便保存下标,然后按顺序从小到大将序列的下标更新到树状数组中,如果下一个询问的h要大于当前询问的h时,则处理该询问,因为我们已经将小于等于h的数全部更新到树状数组中,所以直接区间求和即为答案。
代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=1e5+7; int sum[maxn]; int n,m; struct node{ int h,id; }p[maxn]; struct node1{ int l,r,h,id; }q[maxn]; int ans[maxn]; bool cmp(node x,node y){ return x.h<y.h; } bool cmp1(node1 x,node1 y){ return x.h<y.h; } void update(int pos){ while(pos<=n){ sum[pos]++; pos+=(pos&-pos); } } int ask(int pos){ int res=0; while(pos){ res+=sum[pos]; pos-=(pos&-pos); } return res; } int main(){ int t; scanf("%d",&t); for(int tt=1;tt<=t;tt++){ memset(sum,0,sizeof(sum)); scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){ scanf("%d",&p[i].h); p[i].id=i; } for(int i=1;i<=m;i++){ scanf("%d%d%d",&q[i].l,&q[i].r,&q[i].h); q[i].l++; q[i].r++; q[i].id=i; } sort(p+1,p+1+n,cmp); sort(q+1,q+1+m,cmp1); for(int i=1,j=1;j<=m;j++){ while(i<=n&&p[i].h<=q[j].h){ //将小于等于当前询问h的数的下标更新到树状数组中 update(p[i].id); i++; } ans[q[j].id]=ask(q[j].r)-ask(q[j].l-1); //查找该答案 } printf("Case %d:\n",tt); for(int i=1;i<=m;i++){ printf("%d\n",ans[i]); } } return 0; }
2.分块:
将n个序列分成根号n块,用两个数组同时存序列,a数组为原序列,b数组为每块进行排序后的序列。对于询问操作,如果询问区间只在某块的部分区间的直接暴力,对于整块都在询问区间的块可以采用二分。不过这种方法耗时多一点。
代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=1e5+7; int n,m,a[maxn],b[maxn],num,block,l[maxn],r[maxn],belong[maxn]; void build(){ block=sqrt(n); num=n/block; if(n%block) num++; for(int i=1;i<=num;i++) l[i]=(i-1)*block+1,r[i]=i*block; r[num]=n; for(int i=1;i<=n;i++) belong[i]=(i-1)/block+1; } int ask(int x,int y,int val){ int res=0; if(belong[x]==belong[y]){ for(int i=x;i<=y;i++){ if(a[i]<=val) res++; } return res; } for(int i=x;i<=r[belong[x]];i++){ if(a[i]<=val) res++; } for(int i=l[belong[y]];i<=y;i++){ if(a[i]<=val) res++; } for(int i=belong[x]+1;i<belong[y];i++){ int pos=upper_bound(b+l[i],b+r[i]+1,val)-b-l[i]; res+=pos; } return res; } int main(){ int t; scanf("%d",&t); for(int tt=1;tt<=t;tt++){ printf("Case %d:\n",tt); scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){ scanf("%d",&a[i]); b[i]=a[i]; } build(); for(int i=1;i<=num;i++) sort(b+l[i],b+r[i]+1); while(m--){ int x,y,h; scanf("%d%d%d",&x,&y,&h); x++; y++; printf("%d\n",ask(x,y,h)); } } return 0; }
3.主席树
将主席树的板子的询问操作稍微改改就可以了,对于询问操作,直接先二分查找大于h的第一个编号x,然后查找第l颗线段树到第r颗线段树中小于x编号的和就可以了。
代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=1e5+7; struct node{ int l,r,sum; }tree[maxn*40]; int n,m,cnt,a[maxn],root[maxn]; vector<int> v; int getid(int x){ return lower_bound(v.begin(),v.end(),x)-v.begin()+1; } void update(int l,int r,int &x,int y,int pos){ tree[++cnt]=tree[y],tree[cnt].sum++,x=cnt; if(l==r) return; int mid=(l+r)/2; if(pos<=mid) update(l,mid,tree[x].l,tree[y].l,pos); else update(mid+1,r,tree[x].r,tree[y].r,pos); } int query(int l,int r,int x,int y,int k){ if(l==r) return tree[y].sum-tree[x].sum; int mid=(l+r)/2,ans=0; if(k<=mid) ans+=query(l,mid,tree[x].l,tree[y].l,k); else{ ans+=tree[tree[y].l].sum-tree[tree[x].l].sum; ans+=query(mid+1,r,tree[x].r,tree[y].r,k); } return ans; } int main(){ int T,tot=0; scanf("%d",&T); while(T--){ v.clear(); scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&a[i]),v.push_back(a[i]); sort(v.begin(),v.end()); v.erase(unique(v.begin(),v.end()),v.end()); printf("Case %d:\n",++tot); for(int i=1;i<=n;i++) update(1,n,root[i],root[i-1],getid(a[i])); while(m--){ int l,r,h; scanf("%d%d%d",&l,&r,&h); l++; r++; int x=upper_bound(v.begin(),v.end(),h)-v.begin(); if(x==0) printf("0\n"); else printf("%d\n",query(1,n,root[l-1],root[r],x)); } } return 0; }