HDU-6621【2019 Multi-University Training Contest 4】【主席树】【二分】

K-th Closest Distance

题目链接

Sample Input

1
5 2
31 2 5 45 4
1 5 5 1
2 5 3 2

Sample Output

0
1

样例

第一行 T 组数据
第二行 两个整数 n,m;表示n 个数,m 次查询
第三行 n 个整数(1 <= a[i] <= 1e6)
写下来 m 行,每行4个数,l,r,p,k;

题意

给出一个数组,每次查询把 [ l,r ] 区间的每个数和 p 相减得到的绝对值,找到第 k 小的绝对值,将其输出,另外除了第一次查询,剩下的每次查询的 l,r,p,k 需要与上一次查询的答案进行异或才是真正的 l,r,q,k

思路

题目就是要求区间内距离 p 第 k 近的数,这个数与 p 的距离是多少,我们可以二分距离答案ans,然后判断在[ l , r ] 这个区间里的数在值域 [ p - ans, p + ans ] 里的数有没有 k 个,如果>= k 个,我们就缩小值域范围,即缩小ans,否则就扩大ans;
题解是说线段树加二分,我是用主席树加二分;
因为每个数的范围就 [1,1e6],所以不用离散化,直接把给的数组,建立一颗主席树;然后对答案距离ans进行二分,二分到最后结果就是正确答案

#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#define maxn 100100
using namespace std;

struct node{
	int l, r, sum;
}tree[maxn*40];

int a[maxn], cnt, root[maxn];

void insert(int l, int r, int pre, int &now, int p){
	tree[++cnt] = tree[pre];
	now = cnt;
	tree[now].sum++;
	if(l == r) return;
	int mid = (l+r)>>1;
	if(p <= mid) insert(l, mid, tree[pre].l, tree[now].l, p);
	else insert(mid+1, r, tree[pre].r, tree[now].r, p);
}

int query(int l, int r, int L, int R, int ql, int qr){
	if(ql == l && qr == r){
		return tree[R].sum - tree[L].sum;
	}
	int mid = (l + r)>>1;
	if(qr <= mid){
		return query(l, mid, tree[L].l, tree[R].l, ql, qr);
	}
	else if(ql > mid){
		return query(mid+1, r, tree[L].r, tree[R].r, ql, qr);
	}
	else{
		int ans = query(l, mid, tree[L].l, tree[R].l, ql, mid);
		ans += query(mid+1, r, tree[L].r, tree[R].r, mid+1, qr);
		return ans;
	}
}

void init(){
    cnt = 0;
    tree[cnt].l = 0;
    tree[cnt].r = 0;
    tree[cnt].sum = 0;
    root[cnt] = 0;
}

int main(){
	int t;
	scanf("%d", &t);
	while(t--){
		int n, m, L, R, p, k, l = 1001000, r = -1;
		cnt = 0;
		scanf("%d%d", &n, &m);
		for(int i = 1; i <= n; i++){
			scanf("%d", &a[i]);
			l = min(l, a[i]);
			r = max(r, a[i]);
		}
		for(int i = 1; i <= n; i++) insert(l, r, root[i - 1], root[i], a[i]);
		int ans = 0;
		while(m--){
			scanf("%d%d%d%d", &L, &R, &p, &k);
			int low = 0, up = 1e6, mid;
			L ^= ans;
			R ^= ans;
			p ^= ans;
			k ^= ans;
			while(low <= up){//二分答案
				mid = (low + up)>>1;
				if(query(l, r, root[L-1], root[R], max(l, p - mid), min(r, p + mid)) >= k){
					ans = mid;
					up = mid - 1;
				}
				else low = mid+1;
			}
			printf("%d\n", ans);
		}
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值