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

其中 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 .

 
 

分析:

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

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

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

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

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

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

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

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

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

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

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

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

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

 
 
下面贴代码:

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

原创文章 42 获赞 22 访问量 3045

猜你喜欢

转载自blog.csdn.net/weixin_44327262/article/details/97947629