HDU6621 K-th Closest Distance 第 k 小绝对值(主席树(统计范围的数有多少个)+ 二分 || 权值线段树+二分)

题意:给一个数组,每次给 l ,r, p, k,问区间 [l, r] 的数与 p 作差的绝对值的第 k 小,这个绝对值是多少

 

分析:首先我们先分析单次查询怎么做:

题目给出的数据与多次查询已经在提示着我们在用数据结构去解决这个问题,对于常见的处理区间的数据结构首选线段树啦:

我觉得这道题的关键在于此:我们需要去二分答案ans,  为什么呢? 我们这样观察 ,对于 | p-a[i] | <= ans  等于 p-ans<=a[i] <=p+ans   那问题就转化为查询[L,R] 区间里面在[p-ans,p+ans] 范围的a[i] 有多少个  。这显然是到主席树的题目;

我们明白主席树的原理是多颗权值线段树 , 那我们就把a[i]当成下标,权值为出现的次数,那我们就是查询[L,R] 编号是权值线段树里面[p-ans,p+ans] 范围的a[i] 有多少个 。

 

主席树:

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int tot;
int lson[N],rson[N],sum[N],tr[N];
void build(int &rt,int l,int r){
    rt=++tot;
    if(l==r) return ;
    int mid=(l+r)>>1;
    build(lson[rt],l,mid);
    build(rson[rt],mid+1,r);
}
void updata(int root,int &rt,int p,int l,int r){
    rt=++tot;
    lson[rt]=lson[root],rson[rt]=rson[root];
    sum[rt]=sum[root]+1;
    if(l==r) return ;
    int mid=(l+r)>>1;
    if(p<=mid) updata(lson[root],lson[rt],p,l,mid);
    else updata(rson[root],rson[rt],p,mid+1,r);
}
int query(int rt_,int rt,int L,int R,int l,int r){
    if(l<=L&&R<=r){
        return sum[rt_]-sum[rt];
    }
    int mid=(L+R)>>1;
    int ans=0;
    if(l<=mid)
        ans+=query(lson[rt_],lson[rt],L,mid,l,r);
    if(mid<r)
        ans+=query(rson[rt_],rson[rt],mid+1,R,l,r);
        return ans;
}
int main(){
    int _;scanf("%d",&_);
    while(_--){
        int n,m;scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            {
                int x;scanf("%d",&x);
                updata(tr[i-1],tr[i],x,1,N);
            }
        int T=0;
        for(int i=1;i<=m;i++){
            int L,R,p,k; scanf("%d%d%d%d",&L,&R,&p,&k);
            L^=T,R^=T,p^=T,k^=T;
            int LL=0,RR=1e6,ans=0;
            while(LL<=RR){
                int mid=(LL+RR)>>1;
                int l=max(1,p-mid);
                int r=min(N,p+mid);
                if(query(tr[R],tr[L-1],1,N,l,r)>=k){
                    RR=mid-1;ans=mid;
                }
                else LL=mid+1;
            }
            T=ans;
            printf("%d\n",ans);
        }

    }
    return 0;
}
View Code

权值线段树的做法:

要解决的问题依然没有变;

线段树的每个节点都存着对应区间有序的序列,比如{5,1,2,3,4} ,对于这样的一个序列,线段树的根节点表示的是区间[1,5] 节点里面存有序列{1,2,3,4,5} 那我们查询p-ans<=a[i] <=p+ans  ,就二分{1,2,3,4,5}这个有序的序列有多少个大于p+ans,多少个

大于p-ans-1,在相减;这钟线段树,真的是前所未闻........

#include<bits/stdc++.h>

using namespace std;
const int N=1e5+10;
vector<int>v[N];
vector<int>::iterator it;
int a[N];
void build(int l,int r,int rt){
    v[rt].clear();
    for(int i=l;i<=r;i++)
        v[rt].push_back(a[i]);
    sort(v[rt].begin(),v[rt].end());
    if(l==r) return ;
    int mid=(l+r)>>1;
    build(l,mid,rt<<1);
    build(mid+1,r,rt<<1|1);
}
int query(int L,int R,int rt,int l,int r,int val){
    if(l<=L&&R<=r){
        it=upper_bound(v[rt].begin(),v[rt].end(),val);
        return it-v[rt].begin();
    }
    int ans=0;
    int mid=(L+R)>>1;
    if(l<=mid)
        ans+=query(L,mid,rt<<1,l,r,val);
    if(mid<r)
        ans+=query(mid+1,R,rt<<1|1,l,r,val);
    return ans;
}
int main(){
    int _; scanf("%d",&_);
    while(_--){
        int n,m; scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        build(1,n,1);
        int L1=0,R1=0,p1=0,k1=0;
        int T=0;
        for(int i=1;i<=m;i++){
            int L,R,p,k;
            scanf("%d%d%d%d",&L,&R,&p,&k);
            L^=T,R^=T,p^=T,k^=T;
            int LL=0,RR=1e6,ans=0;
            while(LL<=RR){
                int mid=(LL+RR)>>1;
                int K=query(1,n,1,L,R,p+mid)-query(1,n,1,L,R,p-mid-1);
                if(K>=k) {RR=mid-1;ans=mid;}
                else LL=mid+1;
            }
            printf("%d\n",ans);
            T=ans;
        }
    }
    return 0;
}
View Code

 

posted @ 2019-09-18 12:33  shuai_hui  阅读(192)  评论(0编辑  收藏  举报