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
题目大概意思:
给出长度为 的整数序列,第 个整数为 ,进行 次询问,询问包含 个整数 . 对于每次询问,首先得出真实询问: ,其中 为上一次询问的结果,初始时为 ,然后输出区间 中的元素与 的所有距离中第 小的距离,其中 与 的距离定义为 .
其中 .
分析:
首先想到朴素的求法:对于每次询问,计算
中每个元素与
的距离,并输出这些距离中第
小的。如果通过对这些距离排序来得到第
大的值,一次询问的时间复杂度为
,虽然可以使用快速排序思想在
的期望时间复杂度内得到
大值(可以使用
中的
函数),可是由于查询的次数
很大,这种朴素的求法无法在规定时间内完成。而且由于查询与上一次的结果有关,必须做到在线查询,因此应该选用合理的方式维护数据来做到高效地查询。
考虑到,如果 是区间内距离 第 大的距离,那么在区间 中一定有:
- 与 的距离不超过 的数有不少于 个
- 与 的距离小于 的数有不到 个
因此,如果可以快速求出区间中与
的距离不超过
的数的个数,就可以通过对
进行二分搜索来求出第
小的距离是多少。
接下来,我们来看一下如何计算在某个区间中与 的距离不超过 的数的个数:
如果不进行预处理,那么就只能遍历一遍区间所有的元素。
但另一方面,如果区间是有序的,那么就可以通过二分搜索法高效地求出与 的距离不超过 的数的个数了:
设 为区间中小于 的数的个数, 为区间中小于等于 的数的个数,则区间中与 的距离不超过 的数的个数为 ,而 与 都可以通过二分法在 的时间复杂度内求出。
但是,如果对于每个查询都分别做一次排序,就完全无法降低时间复杂度。所以,可以考虑使用平方分割或线段树进行求解。
下面我们考虑如何使用线段树解决这个问题。我们把数列用线段树维护起来,线段树的每个节点都保存了对应区间排好序后的结果。建立线段树的过程和归并排序的过程类似,所以这样的线段树也叫归并树,而每个节点的数列就是其两个儿子节点的数列合并的结果。建立归并树的时间复杂度和空间复杂度都是是 .
要计算在某个区间中与 的距离不超过 的数的个数,只需要递归地进行如下操作就可以了:
- 如果询问的区间和当前节点维护的区间完全没有交集,那么返回 个。
- 如果询问的区间完全包含了当前节点维护的区间,那么使用二分搜索法查找该节点上与 的距离不超过 的数的个数,并返回该结果。
- 否则对当前节点的两个子节点递归地进行计算之后求和并返回。
由于对于同一深度的节点最多只访问常数个,而归并树的层数为 ,故至多访问 个节点 ,而对每个节点维护的区间进行二分搜索的时间复杂度不超过 ,因此可以在 的时间复杂度内求出与 的距离不超过 的数的个数。由于需要对 的值进行二分搜索,而 的值域与 基本上在同一数量级,故每一次询问的时间复杂度为 ,整个算法的复时间复杂度为 .
下面贴代码:
#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);
}