这个题在比赛中的时候就想着怎么用主席树做,无奈大佬太强,分分钟就过掉了。
题意:给n篇论文,每篇论文分别被引用 ai 次,给q次询问,每次询问给一对 l 和 r,问从l到r最大的x,论文被引用的次数大于等于x的论文最少有x篇,题目很绕。。仔细理解了题意现在就来探讨思路:
思路:我们可以考虑在l-r之间按照论文被引用次数从小到大排好序之后求一遍后缀和,表示从x到r的论文总共有多少篇(论文被引用次数大于等于x次的论文总共有多少篇)。然后就可以想到,哎,主席树刚好就是求的是后缀和或者前缀和,于是此处我们可以通过建一颗主席树来获得从l到r的后缀和,聪明的你们一定知道怎么建,对不对?(邝斌模板走一波,刚好就是后缀和),接下来就是查询啦。我们知道了后缀和就很好棒啦,我们只要在l-r中找到最大的那个x,使得,sum(x-r)>=a[x],这里就用到二分的思想查找啦,左边不对往右边找,右边不对往左边找,只要注意往左边找的时候记得累加右边的那部分后缀和就好啦。具体看代码:(由于数据范围都是1e5,我就没用hash了)
#include
#include
#include
#define ll long long
using namespace std;
const int maxn = 1e5 + 1000;
int a[maxn],t[maxn];
int T[maxn],lson[maxn*30],rson[maxn*30],c[maxn*30];
int n,q,tot;
int build(int l,int r){
int root = tot++;
c[root] = 0;
if(l!=r){
int mid = (l+r)>>1;
lson[root] = build(l,mid);
rson[root] = build(mid+1,r);
}
return root;
}
int updata(int root,int pos,int val){
int newroot = tot++,tmp=newroot;
c[newroot] = c[root] + val;
int l=1,r=maxn;
while(l> 1;
if(pos<=mid){
lson[newroot] = tot++;rson[newroot]=rson[root];
newroot = lson[newroot];root=lson[root];
r=mid;
}
else{
rson[newroot]=tot++;lson[newroot]=lson[root];
newroot=rson[newroot];root=rson[root];
l=mid+1;
}
c[newroot] = c[root] + val;
}
return tmp;
}
int query(int left_root,int right_root){
int l=1,r=maxn;
int v = 0;
while(l>1;
//printf("%d %d %d %d %d\n",c[rson[left_root]],c[rson[right_root]],mid,l,r);
//cout<= mid + 1){
left_root = rson[left_root];
right_root = rson[right_root];
l = mid + 1;
}
else{
v += c[rson[left_root]] - c[rson[right_root]];
left_root = lson[left_root];
right_root = lson[right_root];
r = mid;
}
}
return r;
}
int main(){
while(~scanf("%d%d",&n,&q)){
tot = 0;
memset(c,0,sizeof(c));
memset(T,0,sizeof(T));
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
T[n+1] = build(1,maxn);
for(int i=n;i>=1;i--){
T[i] = updata(T[i+1],a[i],1);
}
while(q--){
int l,r;
scanf("%d%d",&l,&r);
printf("%d\n",query(T[l],T[r+1]));
//printf("%d %d %d\n",c[T[r]],c[T[l]],c[T[l]] - c[T[r]]);
}
}
return 0;
}