HDU6621 K-th Closest Distance - 线段树 - 归并树 - 二分搜索法

K-th Closest Distance

Time Limit: 20000/15000 MS (Java/Others)   Memory Limit: 524288/524288 K (Java/Others)
Total Submission(s): 236   Accepted Submission(s): 97

Problem Description

You have an array: a1, a2, …, an and you must answer for some queries.
For each query, you are given an interval [L, R] and two numbers p and K. Your goal is to find the Kth closest distance between p and aL, aL+1, …, aR.
The distance between p and ai is equal to |p - ai|.
For example:
A = {31, 2, 5, 45, 4 } and L = 2, R = 5, p = 3, K = 2.
|p - a2| = 1, |p - a3| = 2, |p - a4| = 42, |p - a5| = 1.
Sorted distance is {1, 1, 2, 42}. Thus, the 2nd closest distance is 1.

Input

The first line of the input contains an integer T (1 <= T <= 3) denoting the number of test cases.
For each test case:
冘The first line contains two integers n and m (1 <= n, m <= 10^5) denoting the size of array and number of queries.
The second line contains n space-separated integers a1, a2, …, an (1 <= ai <= 10^6). Each value of array is unique.
Each of the next m lines contains four integers L’, R’, p’ and K’.
From these 4 numbers, you must get a real query L, R, p, K like this:
L = L’ xor X, R = R’ xor X, p = p’ xor X, K = K’ xor X, where X is just previous answer and at the beginning, X = 0.
(1 <= L < R <= n, 1 <= p <= 10^6, 1 <= K <= 169, R - L + 1 >= K).

Output

For each query print a single line containing the Kth closest distance between p and aL, aL+1, …, aR.

Sample Input

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

Sample Output

0
1

 
 

题目大概意思:

给出长度为 n ( 1 ≤ n ≤ 1 0 5 ) n(1≤n≤10^5) n(1n105) 的整数序列,第 i i i 个整数为 a i ( 1 ≤ a i ≤ 1 0 6 ) a_i(1≤a_i≤10^6) ai(1ai106) ,进行 m ( 1 ≤ m ≤ 1 0 5 ) m(1≤m≤10^5) m(1m105) 次询问,询问包含 4 4 4 个整数 L ′ , R ′ , p ′ , K ′ L&#x27;,R&#x27;,p&#x27;,K&#x27; L,R,p,K . 对于每次询问,首先得出真实询问: L = L ′ L=L&#x27; L=L x o r xor xor X , X, X, R = R ′ R=R&#x27; R=R x o r xor xor X , X, X, p = p ′ p=p&#x27; p=p x o r xor xor X , X, X, K = K ′ K=K&#x27; K=K x o r xor xor X X X ,其中 X X X 为上一次询问的结果,初始时为 0 0 0 ,然后输出区间 [ L , R ] [L,R] [L,R] 中的元素与 p p p 的所有距离中第 K K K 小的距离,其中 a i a_i ai p p p 的距离定义为 ∣ p − a i ∣ |p-a_i| pai .

其中 1 ≤ L &lt; R ≤ n , 1 ≤ p ≤ 1 0 6 , 1 ≤ K ≤ 169 , R − L + 1 ≥ K 1≤L&lt;R≤n,1≤p≤10^6,1≤K≤169,R-L+1≥K 1L<Rn,1p106,1K169,RL+1K .

 
 

分析:

首先想到朴素的求法:对于每次询问,计算 [ L , R ] [L,R] [L,R] 中每个元素与 p p p 的距离,并输出这些距离中第 K K K 小的。如果通过对这些距离排序来得到第 K K K 大的值,一次询问的时间复杂度 O ( n ⋅ log ⁡ 2 n ) O(n·\log_2{n}) O(nlog2n) ,虽然可以使用快速排序思想在 O ( n ) O(n) O(n) 的期望时间复杂度内得到 K K K 大值(可以使用 S T L STL STL 中的 n t h _ e l e m e n t nth\_element nth_element 函数),可是由于查询的次数 m m m 很大,这种朴素的求法无法在规定时间内完成。而且由于查询与上一次的结果有关,必须做到在线查询,因此应该选用合理的方式维护数据来做到高效地查询。
 

考虑到,如果 x x x 是区间内距离 p p p K K K 大的距离,那么在区间 [ L , R ] [L,R] [L,R] 中一定有:

  1. p p p 的距离不超过 x x x 的数有不少于 K K K
  2. p p p 的距离小于 x x x 的数有不到 K K K

因此,如果可以快速求出区间中与 p p p 的距离不超过 x x x 的数的个数,就可以通过对 x x x 进行二分搜索来求出第 K K K 小的距离是多少。
 

接下来,我们来看一下如何计算在某个区间中与 p p p 的距离不超过 x x x 的数的个数:

如果不进行预处理,那么就只能遍历一遍区间所有的元素。

但另一方面,如果区间是有序的,那么就可以通过二分搜索法高效地求出与 p p p 的距离不超过 x x x 的数的个数了:

c n t L cnt_L cntL 为区间中小于 p − x p-x px 的数的个数, c n t R cnt_R cntR 为区间中小于等于 p + x p+x p+x 的数的个数,则区间中与 p p p 的距离不超过 x x x 的数的个数为 c n t R − c n t L cnt_R-cnt_L cntRcntL ,而 c n t L cnt_L cntL c n t R cnt_R cntR 都可以通过二分法在 O ( log ⁡ 2 L e n g t h ) O(\log_2{Length}) O(log2Length) 的时间复杂度内求出。

但是,如果对于每个查询都分别做一次排序,就完全无法降低时间复杂度。所以,可以考虑使用平方分割线段树进行求解。

 

下面我们考虑如何使用线段树解决这个问题。我们把数列用线段树维护起来,线段树的每个节点都保存了对应区间排好序后的结果。建立线段树的过程和归并排序的过程类似,所以这样的线段树也叫归并树,而每个节点的数列就是其两个儿子节点的数列合并的结果。建立归并树的时间复杂度和空间复杂度都是是 O ( n ⋅ log ⁡ 2 n ) O(n·\log_2{n}) O(nlog2n) .

要计算在某个区间中与 p p p 的距离不超过 x x x 的数的个数,只需要递归地进行如下操作就可以了:

  1. 如果询问的区间和当前节点维护的区间完全没有交集,那么返回 0 0 0 个。
  2. 如果询问的区间完全包含了当前节点维护的区间,那么使用二分搜索法查找该节点上与 p p p 的距离不超过 x x x 的数的个数,并返回该结果。
  3. 否则对当前节点的两个子节点递归地进行计算之后求和并返回。

 

由于对于同一深度的节点最多只访问常数个,而归并树的层数为 log ⁡ 2 n + 1 \log_2{n}+1 log2n+1 ,故至多访问 O ( l o g 2 n ) O(log_2{n}) O(log2n) 个节点 ,而对每个节点维护的区间进行二分搜索的时间复杂度不超过 O ( l o g 2 n ) O(log_2{n}) O(log2n) ,因此可以在 O ( l o g 2 2 n ) O(log_2^2{n}) O(log22n) 的时间复杂度内求出与 p p p 的距离不超过 x x x 的数的个数。由于需要对 x x x 的值进行二分搜索,而 x x x 的值域与 n n n 基本上在同一数量级,故每一次询问的时间复杂度为 O ( log ⁡ 2 3 n ) O(\log_2^3{n}) O(log23n) ,整个算法的复时间复杂度为 O ( n ⋅ log ⁡ 2 n + m ⋅ log ⁡ 2 3 n ) O(n·\log_2{n}+m·\log_2^3{n}) O(nlog2n+mlog23n) .

 
 
下面贴代码:

#include <cstdio>
#include <algorithm>
using namespace std;


const int INF = 1 << 27;
const int MAX_H = 18;
const int MAX_N = 1 << (MAX_H - 1);

int A[MAX_N];

int dat[MAX_H][MAX_N];
int _n, _h;

void init(const int N);
int query(const int& left, const int& right, const int& x, const int h, const int begin, const int end);
inline int cnt(const int& left, const int& right, const int lx, const int rx);

int main()
{
	int T, n, m;
	scanf("%d", &T);

	while (T--)
	{
		scanf("%d%d", &n, &m);

		int maxa = -1, mina = INF;
		for (int i = 0; i < n; ++i)
		{
			int& cur = A[i];
			scanf("%d", &cur);
			if (cur > maxa) maxa = cur;
			if (cur < mina) mina = cur;
		}
		init(n);

		int maxd = maxa - mina + 1;

		int L, R, p, K, X = 0;
		while (m--)
		{
			scanf("%d%d%d%d", &L, &R, &p, &K);
			L ^= X;
			R ^= X;
			p ^= X;
			K ^= X;

			--L, --R;
			int lb = -1, rb = maxd, mid;
			while (lb + 1 < rb)
			{
				mid = (lb + rb) >> 1;
				if (cnt(L, R, p - mid, p + mid) < K)
				{
					lb = mid;
				}
				else
				{
					rb = mid;
				}
			}
			X = rb;
			printf("%d\n", rb);
		}
	}
	return 0;
}

// 构建归并树
void init(const int N)
{
	_n = 1;
	_h = 0;
	while (_n < N)
	{
		_n <<= 1;
		++_h;
	}
	--_n;

	memcpy(dat[_h], A, N * sizeof(int));

	for (int h = _h, d = 1; h; --h, d <<= 1)
	{
		const int* const& cur = dat[h];
		for (int p = 0; p < _n; p += d << 1)
		{
			merge(cur + p, cur + p + d, cur + p + d, cur + p + (d << 1), dat[h - 1] + p);
		}
	}
}

// 区间内小于 x 的个数
int query(const int& left, const int& right, const int& x, const int h, const int begin, const int end)
{
	if (left <= begin && end <= right)
	{
		return lower_bound(dat[h] + begin, dat[h] + end + 1, x) - dat[h] - begin;
	}
	else if (left <= end && begin <= right)
	{
		return query(left, right, x, h + 1, begin, (begin + end) >> 1)
			+ query(left, right, x, h + 1, ((begin + end) >> 1) + 1, end);
	}
	else return 0;
}

inline int cnt(const int& left, const int& right, const int lx, const int rx)
{
	return  query(left, right, rx + 1, 0, 0, _n) - query(left, right, lx, 0, 0, _n);
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值