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;
}

权值线段树的做法:

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

线段树的每个节点都存着对应区间有序的序列,比如{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;
}

原文地址:https://www.cnblogs.com/shuaihui520/p/11541240.html

时间: 2024-07-29 23:33:58

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

【bzoj3065】带插入区间K小值 替罪羊树套权值线段树

题目描述 从前有n只跳蚤排成一行做早操,每只跳蚤都有自己的一个弹跳力a[i].跳蚤国王看着这些跳蚤国欣欣向荣的情景,感到非常高兴.这时跳蚤国王决定理性愉悦一下,查询区间k小值.他每次向它的随从伏特提出这样的问题: 从左往右第x个到第y个跳蚤中,a[i]第k小的值是多少.这可难不倒伏特,他在脑袋里使用函数式线段树前缀和的方法水掉了跳蚤国王的询问.这时伏特发现有些跳蚤跳久了弹跳力会有变化,有的会增大,有的会减少.这可难不倒伏特,他在脑袋里使用树状数组套线段树的方法水掉了跳蚤国王的询问.(orz 主席

【BZOJ3110】【codevs1616】K大数查询,权值线段树套普通线段树

Time:2016.05.09 Author:xiaoyimi 转载注明出处谢谢 传送门1 传送门2 思路: 之前没怎么接触过权值线段树(非主席树),这次就当学习了一下吧.一开始还把题意理解错了,我的天啊-- 起初思考了好久,发现不知道怎么处理负数的情况,不过数据里并没有负数? 权值线段树的每个节点表示一个区间[L,R],存储原序列中权值为[L,R]的元素的信息,所以这里的权值线段树每个节点上都是一棵普通线段树,就是负责统计原序列中权值为[L,R]的元素的个数. 每次插入一个相同的值x时就相当于

【BZOJ3110】【Zjoi2013】K大数查询 树套树 权值线段树套区间线段树

#include <stdio.h> int main() { puts("转载请注明出处谢谢"); puts("http://blog.csdn.net/vmurder/article/details/43020009"); } 题解: 外层权值线段树,内层区间线段树可解. 权值都是1~n,就不用离散化了. 我写了标记永久化. 其它心得神马的: 天生对树形数据结构无爱. 第一次写树套树,终于知道是怎么回事了. (只针对本题) 就是外层每个点都表示了一段

BZOJ 3110 ZJOI 2013 K大数查询 树套树(权值线段树套区间线段树)

题目大意:有一些位置,这些位置上可以放若干个数字.现在有两种操作. 1.在区间l到r上添加一个数字x 2.求出l到r上的第k大的数字是什么 思路:这种题一看就是树套树,关键是怎么套,怎么写.(话说我也不会来着..)最容易想到的方法就是区间线段树套一个权值线段树,但是区间线段树上的标记就会变得异常复杂.所以我们就反过来套,用权值线段树套区间线段树.这样修改操作在外线段树上就变成了单点修改,外线段树就不用维护标记了.在里面的区间线段树上维护标记就容易多了.具体实现见代码. CODE: #includ

动态求区间K大值(权值线段树)

我们知道我们可以通过主席树来维护静态区间第K大值.我们又知道主席树满足可加性,所以我们可以用树状数组来维护主席树,树状数组的每一个节点都可以开一颗主席树,然后一起做. 我们注意到树状数组的每一棵树都和前一颗树没有关系,so,并不需要可持久化,一个朴素的权值线段树就可以啦. 我们知道普通的线段树是刚开始就把所有的节点都开了,但我们发现并不需要,因为每个点里的操作并不是很多,很大一部分的节点是用不到的,那么我们就可以不开.用Ls 和 Rs 来记左右儿子的地址,随用随开即可. #include<bit

区间第k大问题 权值线段树

先说下权值线段树的概念吧 待整理 附上吴迎学长离散化的标准代码 #include<iostream> #include<algorithm> #include<cstdio> using namespace std; int a[100],b[100],c[100],n; int solve()//离散化 { for(int i=0;i<n;i++) b[i]=a[i]; sort(b,b+n); int m=unique(b,b+n)-b;//去重 for(in

HDU6464 (权值线段树)-(查找区间第k1小于第k2小之间的和)

http://acm.hdu.edu.cn/showproblem.php?pid=6464 不理解先看博客:https://blog.csdn.net/g21glf/article/details/82986968 已知一开始有一个空序列,接下来有Q次操作,每次操作给出type.first和second三个值.当type为1时,意味着该操作属于第一种操作:往序列尾部添加first个second数.当type为2时,意味着该操作属于第二种操作:查询序列中第first小至第second小的数值之和

【XSY2720】区间第k小 整体二分 可持久化线段树

题目描述 给你你个序列,每次求区间第\(k\)小的数. 本题中,如果一个数在询问区间中出现了超过\(w\)次,那么就把这个数视为\(n\). 强制在线. \(n\leq 100000,a_i<n,w\leq n\) 题解 考虑整体二分. 先看看离线要怎么做. 现在我们要计算每个数对每个区间的贡献. 对于每个询问区间和每种数,让这个区间最右边\(w\)个数对这个询问的贡献为\(1\),第\(w+1\)个数对这个询问的贡献为\(-w\). 这样每个数的贡献就是二维平面上的一个矩形.可以用扫描线+线段

[bzoj3065] 带插入区间第k小值 [重量平衡树套线段树]

题面 传送门 思路 发现强制在线了...... 本来可以树套树解决的问题,现在外层不能使用线段树了,拿什么替代呢? 我们需要一种支持单点插入.下套数据结构.数据结构上传合并复杂度最多单log,不能旋转的数据结构 这不是摆明了用重量平衡树吗? 我选了替罪羊树作为上层结构,下面套了一棵线段树,就做完了 查询的时候把替罪羊树上对应的log个区间提取出来,一起在底层权值线段树上二分即可 详见代码注释 Code #include<iostream> #include<cstdio> #inc