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 .

 
 

分析:

此题可以通过建立归并树,在 O ( log 2 2 n ) O(\log_2^2{n}) 的时间复杂度内求出区间内与 p p 的距离不超过 x x 的数的个数,并通过对 x x 的值进行二分搜索在 O ( log 2 3 n ) O(\log_2^3n) 的时间复杂度内完成一次询问,这种算法的总时间复杂度是 O ( n log 2 n + m log 2 3 n ) O(n·\log_2{n}+m·\log_2^3{n}) . 使用归并树的解法可以参考我的另一篇博客

然而这一问题还可以通过运用可持久化的权值线段树(主席树),在 O ( log 2 n ) O(\log_2n) 的时间复杂度内更高效地求出区间内与 p p 的距离不超过 x x 的数的个数,并通过对 x x 的值进行二分搜索在 O ( log 2 2 n ) O(\log_2^2n) 的时间复杂度内完成一次询问,时间上优于归并树。而建树的时间复杂度与归并树相同,都是 O ( n log 2 n ) O(n·\log_2n) ,因此总的时间复杂度是 O ( n log 2 n + m log 2 2 n ) O(n·\log_2n+m·\log_2^2n) .

在这种算法中,我们使用线段树来维护每一个值出现的次数,即建立起权值线段树,并运用可持久化的方法,记录 n + 1 n+1 棵权值线段树的历史版本。在每次询问时,根据次数的可减性, T r e e R T r e e L 1 Tree_R-Tree_{L-1} 即为区间 [ L , R ] [L,R] 上的权值线段树,接下来按照普通的线段树询问方法,询问值在区间 [ p x , p + x ] [p-x,p+x] 内的数的出现次数之和即可。由于最多访问 2 2 棵线段树上不超过 O ( log 2 n ) O(\log_2n) 数量的节点,故可以在 O ( log 2 n ) O(\log_2n) 的时间复杂度内求出与 p p 的距离不超过 x x 的数的个数。

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

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

因此可以通过对 x x 的值进行二分,在 O ( log 2 2 n ) O(\log_2^2n) 的时间复杂度内求出 x x 的值。

考虑到 a i a_i 的取值范围为 [ 1 , 1 0 6 ] [1,10^6] ,高出 n n 的取值范围一个数量级,因此有必要先对数据进行离散化处理来减少每棵权值线段树维护的节点数,提高时间与空间效率。

 
 
下面贴代码:

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

const int MAX_N = 100005;

struct P
{
	int lson;
	int rson;
	int sum;

	P() {}
	P(const int& lson, const int& rson, const int& sum)
		:lson(lson), rson(rson), sum(sum) {}
};
P dat[MAX_N << 5];
int ncnt, _n;

int A[MAX_N], B[MAX_N], Th[MAX_N];

void init(const int n);
int build(const int begin, const int end);
int update(const int& x, const int pre, const int begin, const int end);
int query(const int& left, const int& right, const int k, 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--)
	{
		ncnt = 0;
		scanf("%d%d", &n, &m);

		for (int i = 1; i <= n; ++i)
		{
			scanf("%d", A + i);
		}
		init(n);

		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;

			int lb = -1, rb = B[_n] - B[1], mid;
			while (lb + 1 < rb)
			{
				mid = (lb + rb) >> 1;
				int lx = lower_bound(B + 1, B + _n + 1, p - mid) - B;
				int rx = upper_bound(B + 1, B + _n + 1, p + mid) - B - 1;
				if (cnt(L, R, lx, rx) < K)
				{
					lb = mid;
				}
				else
				{
					rb = mid;
				}
			}
			X = rb;
			printf("%d\n", rb);
		}
	}

	return 0;
}

void init(const int n)
{
	memcpy(B + 1, A + 1, n * sizeof(int));
	sort(B + 1, B + n + 1);
	_n = unique(B + 1, B + n + 1) - B - 1;

	Th[0] = build(1, _n);

	for (int i = 1; i <= n; ++i)
	{
		int tmp = lower_bound(B + 1, B + _n + 1, A[i]) - B;
		Th[i] = update(tmp, Th[i - 1], 1, _n);
	}
}

int build(const int begin, const int end)
{
	int rt = ++ncnt;
	P& cur = dat[rt];
	cur.sum = 0;
	if (begin < end)
	{
		int mid = (begin + end) >> 1;
		cur.lson = build(begin, mid);
		cur.rson = build(mid + 1, end);
	}
	return rt;
}

int update(const int& x, const int pre, const int begin, const int end)
{
	int rt = ++ncnt;
	P& cur = dat[rt];
	cur = dat[pre];
	++cur.sum;

	if (begin < end)
	{
		int mid = (begin + end) >> 1;
		if (x <= mid)
		{
			cur.lson = update(x, dat[pre].lson, begin, mid);
		}
		else
		{
			cur.rson = update(x, dat[pre].rson, mid + 1, end);
		}
	}
	return rt;
}

int query(const int& left, const int& right, const int k, const int begin, const int end)
{
	if (left <= begin && end <= right)
	{
		return dat[k].sum;
	}
	else if (left <= end && begin <= right)
	{
		const P& cur = dat[k];
		return query(left, right, cur.lson, begin, (begin + end) >> 1)
			+ query(left, right, cur.rson, ((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(lx, rx, Th[right], 1, _n) - query(lx, rx, Th[left - 1], 1, _n);
}

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

猜你喜欢

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