杭电2019多校第四场 HDU-6621 K-th Closest Distance(主席树+二分)

链接:http://acm.hdu.edu.cn/showproblem.php?pid=6621


题意:T组样例(T<=3)。每组样例第一行给出n、q。接下来给出n个数。在接下来q个询问,每个询问给出l、r、p、k,但都要异或上一次的答案,才是真正的值。(也就是只能在线处理,不能离线处理。)每次询问,输出[l,r]区间内,第k小的|p-a[i]|。

思路:边更新边建立主席树,并且每次都不初始化。因为主席树本来就是每次插入值然后建起来的,即使是多个样例共用这个主席树也没事。对于每次询问,我们二分枚举a[i],看在[l,r]内,[p-a[i],p+a[i]]的个数是否大于等于k个,是得话就缩小a[i],否则就扩大a[i],这样一定能找到刚好只有k个值在该区间的a[i]。果然主席树不止差区间第k大,还可以查区间内在某个区间内的值的个数。详情请看注释。

PS:%%%%%%%%%坤神,tql。orzorzorzorzorzorzorzorzorzorzorzorzorzorzorzorzorzorzorzorzorz

#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
using namespace std;
#define M(a, b) memset(a, b, sizeof(a))
#define lowbit(x) (x&(-x))
typedef long long ll;
const int N=1e5+10;
const int M=1e6; 
struct node
{
    int l,r;
    //l:其左子树的编号,即tree数组的下标
	//r:其右子树的编号,即tree数组的下标 
    int val;
}tree[N * 55];
int n,q,a[N],root[N],cnt,limit,b[N],num;
int build(int l,int r)
{
    int cur=cnt++;
    tree[cur].val=0;
    if(l==r)
    {
        
        tree[cur].l=0; 
        tree[cur].r=0;
        return cur;
    }
    int mid=(r+l)>>1;
    tree[cur].l=build(l,mid);
    tree[cur].r=build(mid+1,r);
    return cur;
}
int update(int up,int tar,int l,int r)
{
	//给新节点编号 
    int cur=cnt++;
    //主席树相当于前缀,所以记下上一个树节点的值
	//相当于pre[i]=pre[i-1] 
    tree[cur]=tree[up];
    //这一步相当于pre[i]=pre[i-1]+a[i]中的a[i]
	//因为主席树记的是数的个数 
    tree[cur].val++;
    //如果到了叶节点便返回 
    if(l==r) return cur;
    int mid=(r+l)>>1;
    //相当于线段树的点更新,只不过还要更新左或右子树的编号 
    if(tar<=mid) tree[cur].l=update(tree[up].l,tar,l,mid);
    else tree[cur].r=update(tree[up].r,tar,mid+1,r);
    return cur;
}
 
int query(int pl,int pr,int l,int r,int tl,int tr) 
{	 
    if(pl<=l&&r<=pr)
    {
    	//前缀的思想 
        return tree[tr].val-tree[tl].val;
    }
    
    int m=(l+r)>>1,ans=0;
    //查左右子树,传入左右子树的编号 
    if(pl<=m) ans+=query(pl,pr,l,m,tree[tl].l,tree[tr].l);
    if(pr>=m+1)  ans+=query(pl,pr,m+1,r,tree[tl].r,tree[tr].r);
    return ans;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&q);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            root[i]=update(root[i-1],a[i],1,M);            
        }
        //由于T很小,不必重新建树,空间换时间 
      	//cnt=0;
     	//root[0]=build(1,M);
        int l,r,k,p,ll,rr,m;
        int ans=0;
        while(q--)
        {
            scanf("%d%d%d%d",&l,&r,&p,&k);
            l^=ans; r^=ans;
            p^=ans; k^=ans;
            if(l>r) swap(l,r);
            //这里答案可能为0,所以ll初始化为0。 
            ll=0; rr=M;
            while(ll<=rr)
            {
                m=(ll+rr)>>1;
                //因为主席树l、r记的是编号,所以要多2个参数,记下编号 
                if(query(max(1,p-m),min(p+m,M),1,M,root[l-1],root[r])>=k)
                {
                    ans=m;
                    rr=m-1;
                }
                else 
                    ll=m+1;
            }
            printf("%d\n",ans);
        }
    }
    return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值