【HDU6621 K-th Closest Distance】主席树+二分答案

本文介绍使用主席树解决一个复杂的数据结构问题:在给定数列中,对多个询问区间进行距离第k近的元素查找,并将结果按位异或。文章详细解释了算法思路,包括如何利用二分法和主席树查询特定区间的元素数量,以及实现过程中的注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

传送门~!

真 · 弱鸡解读,大佬退散 /// >。< ///

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

特别感谢zzy小公举对着我满是bug的程序改了一晚上,辛苦啦~

题意

给定一个数列,有若干询问,询问 l~r 区间内,距离p第k近的元素与p的距离。每次询问内容都要与上次询问的答案按位异或,初始答案为0。

思路

首先和区间第k大有关,肯定是要用主席树啦~
其次因为问题问的不是区间第k大,而是第k小距离,但距离这个东西是相对于询问中给的p而言的,我们显然不可能每次根据p去修改主席树。
那么有没有什么方法,可以让这个距离反映在原数列上,且与p无关呢?
可以想,距离第k小,假设这个距离为 x,其实也就是相当于在询问的区间里, [ p − x , p + x ] [p-x,p+x] [px,p+x]范围内的数有k个。(闭区间哦)。
这样的话,我们可以不断地猜测这个x,然后通过查询区间里 [ p − x , p + x ] [p-x,p+x] [px,p+x]范围内的数的个数来验证我们的答案,也就是二分答案啦。

需要注意的许多细节

都是我错过的
这次的树根集合 T [ i ] T[i] T[i]表示的是权值!(感觉是会好理解一丢丢。。)
T [ i ] T[i] T[i]表示第0~i小的数,所以求的时候看起来大概就是 T [ p + x ] − T [ p − x − 1 ] T[p+x]-T[p-x-1] T[p+x]T[px1]类似的亚子。

  1. 许多主席树的板子在离散化的时候都用了sort+unique函数,但是不知道为什么,这题不可以。(也许是我用得不对,求知情dalao指正)。
    这里我用的是直接以 a [ i ] a[i] a[i]的值对 i d [ i ] id[i] id[i](数字在原数列中的位置)排序,在建树的时候再依照原先的下标,从1到n,插入 i d [ i ] id[i] id[i]。可以认为 i d [ i ] id[i] id[i]是离散的之后 a [ i ] a[i] a[i],因为题目保证了 a [ i ] a[i] a[i]互不相同,在排序之后,原来有序的 id 就会从 ai 的位置代号变成 ai 的大小编号(自己在纸上试试就知道啦),所以就代替了原来的一顿操作啦。
  2. 因为是距离嘛,虽然保证了 a [ i ] a[i] a[i]互不相同,但是距离相同的数还是会有的。所以要处理好边界。
    以我的程序为例。
while(L <= R){
    mid = (L+R)>>1;
    lb = lower_bound(t+1, t+n+1, p-mid)-t;
    ub = upper_bound(t+1, t+n+1, p+mid)-t-1;
    ans = 0;
    query(1,n,T[lb-1],T[ub],l,r);
    if(ans >= k) { 
        x = mid;
		R = mid-1;
    }
    else L = mid+1;
}

求出ans之后,它可能大于等于k,也可能小于k,如果小于k是肯定不行了,但是如果大于等于k。。
打个比方,比如有一群数,他们的距离是1 1 2 2 3,我们 要寻找距离第3大。
我们二分答案,假设开始时 l=0,r=3,mid=1,假设ans表示距离小于等于mid的数的个数。这时候求出来 ans=2,小于我们要找的3,所以 l 变成 mid+1。
现在 l=2,r=3,mid=2,这次求出来 ans=4,大于k了,但是我们肉眼观察就可以知道,mid=2 是正确的半径。于是我们应该在 ans>=k 的时候先把半径记录下来,然后继续循环。 r 还是要等于 mid-1 啦。
现在 l=2,r=1,不满足循环条件了,于是退出循环~(这例子不够长,不过大概就是这么个意思。。)

  1. 查询函数query里边的变量的含义一定要明确,不要把自己转晕。
    比如我的query是这个样子的。
void query(int l,int r,int lr,int rr,int L,int R)

其中,l 和 r 是询问的总区间,初始永远是 1~ n,经过递归变成 L ~ R。萌新(说的就是我自己 )注意哦,在这一题里,n在这里代表的是数列的总长度 n ,不是多少种权值的那个n。
L、R是询问的区间,嗯,就是q组询问那个询问,把它定义成全局变量就不用每次都传进去了。
lr 和 rr ,也就是 left_root 和 right_root。因为主席树要用两棵线段树做差,求出权值位于其中间的树的个数,所以我们需要这两棵树的树根。
那么,树根到底怎么找到呢?
我们现在已知的是一个区间 [ p − x , p + x ] [p-x,p+x] [px,p+x],我们可以求出区间端点对应的“第几大”,然后就可以啦。
感觉自己说的全是废话

代码全文

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100010;
const int M = MAXN * 30;
int n,q,m,tot,ans=0,k;
int t[MAXN];
int T[M], lson[M], rson[M], c[M],id[MAXN];
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 update(int root,int pos,int val){
    int newroot=tot++,tmp=newroot;
    c[newroot]=c[root]+val;
    int l=1,r=n;
    while(l<r){
        int mid=(l+r)>>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;
}
//返回lk~rk的数的个数 不包含端点
void query(int l,int r,int lr,int rr,int L,int R){
    if(ans >= k) return;
    if(L <= l &&r <= R){
        ans += c[rr]-c[lr];
        return;
    }
    int mid = (l+r)>>1;    
    if(L <= mid) query(l,mid,lson[lr],lson[rr],L,R);
    if(mid < R) query(mid+1,r,rson[lr],rson[rr],L,R);
    return ;
}
bool cmp(int x,int y){
    return t[x]<t[y];
}
int main(){
    int Tc;
    scanf("%d",&Tc); 
    while(Tc --){
        scanf("%d%d",&n,&q);
        tot = 0;
        for(int i = 1;i <= n;i++)
            scanf("%d",&t[i]),id[i]=i;
        sort(id+1,id+1+n,cmp);
        sort(t+1,t+n+1);
        T[0]= build(1,n);
        for(int i=1;i<=n;i++){
            T[i]=update(T[i-1],id[i],1);
        }
        int l,r,p,x=0,tmp,L,R,mid,lb,ub;
        while(q--){
            scanf("%d%d%d%d",&l,&r,&p,&k);
            l ^= x;r ^= x;p ^= x;k ^= x;
            L=0, R=1e6;
            while(L <= R){
                mid = (L+R)>>1;
                lb = lower_bound(t+1, t+n+1, p-mid)-t;
                ub = upper_bound(t+1, t+n+1, p+mid)-t-1;
                ans = 0;
                query(1,n,T[lb-1],T[ub],l,r);
                if(ans >= k) {
                    x = mid;
					R = mid-1;
                }
                else L = mid+1;
            }
            printf("%d\n",x);
        }
    }
    return 0;
}

后记

主席树萌新写这个题真的是瑟瑟发抖啊,算起来也是奋斗了一天半呢,眼睛都被辣哭了,心态也是各种崩。
再次感谢zzy小公举啦!还要de玄学bug,还要忍受我的暴脾气。
不过感觉收获也是很多的。
首先是写什么题目,一定要想好再下手啊!对于每个变量是干嘛的,一定要写清楚!不要乱起名字,也不要想当然。
其次,玄学的、不能驾驭的东西,还是不要乱用啦。能驾驭的话省时省力固然好,但是如果不是很清楚的话,还是宁愿多费点功夫,也不要去碰运气啦。
还是要明白,自己在写什么吧。

三水小姐姐好厉害嘤嘤嘤!!!!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值