ZOJ 2112 Dynamic Rankings(动态求区间第k大+整体二分)

Dynamic Rankings

Time Limit: 10 Seconds       Memory Limit: 32768 KB

The Company Dynamic Rankings has developed a new kind of computer that is no longer satisfied with the query like to simply find the k-th smallest number of the given N numbers. They have developed a more powerful system such that for N numbers a[1], a[2], ..., a[N], you can ask it like: what is the k-th smallest number of a[i], a[i+1], ..., a[j]? (For some i<=j, 0<k<=j+1-i that you have given to it). More powerful, you can even change the value of some a[i], and continue to query, all the same.

Your task is to write a program for this computer, which

- Reads N numbers from the input (1 <= N <= 50,000)

- Processes M instructions of the input (1 <= M <= 10,000). These instructions include querying the k-th smallest number of a[i], a[i+1], ..., a[j] and change some a[i] to t.


Input

The first line of the input is a single number X (0 < X <= 4), the number of the test cases of the input. Then X blocks each represent a single test case.

The first line of each block contains two integers N and M, representing N numbers and M instruction. It is followed by N lines. The (i+1)-th line represents the number a[i]. Then M lines that is in the following format

Q i j k or
C i t

It represents to query the k-th number of a[i], a[i+1], ..., a[j] and change some a[i] to t, respectively. It is guaranteed that at any time of the operation. Any number a[i] is a non-negative integer that is less than 1,000,000,000.

There're NO breakline between two continuous test cases.


Output

For each querying operation, output one integer to represent the result. (i.e. the k-th smallest number of a[i], a[i+1],..., a[j])

There're NO breakline between two continuous test cases.


Sample Input

2
5 3
3 2 1 4 7
Q 1 4 3
C 2 6
Q 2 5 3
5 3
3 2 1 4 7
Q 1 4 3
C 2 6
Q 2 5 3


Sample Output

3
6
3

6

solution:

动态求区间第k大,可用树状数组套主席树,但对于这道题来说会TLE,其他树套树都不如整体二分快。

将修改看成是删除和添加,那么总共有添加、删除和查询三种操作。

这个判定操作是属于左区间还是右区间的过程就是通过比较比二分的mid大的数的个数和k。同时我们看到,如果比二分的mid小的数的个数小于k了,我们是要去寻找大的答案,那么这些比mid大的数在以后的递归里始终会对答案有贡献,所以我们没必要去做重复的工作,只需要把这些数的个数累积到贡献里,以后递归的时候就不用考虑这些数了。如果比二分的mid小的数大于k,就要寻找小的答案,那么那些本来就比mid大的添加和删除操作对答案不会产生影响,可以放在答案右区间。

详细见代码~

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#define mem(a) memset(a,0,sizeof(a))
const int maxn = 220000;
const int inf = 1e9 + 7;
using namespace std;
struct query
{
	int x, y, k, s, tp, cur;//添加:tp=1 删除:tp=2 查询:tp=3
}q[maxn], q1[maxn], q2[maxn];
int a[maxn], ans[maxn], tmp[maxn], bit[maxn];
int n, m, num, cnt;//cnt为查询的顺序
void init()
{
	cnt = 0;num = 0;
	mem(bit); mem(tmp); mem(q);
}
void add(int x, int y)
{
	for (int i = x; i <= n; i += (i&-i))
		bit[i] += y;
}
int sum(int x)
{
	int tmp = 0;
	for (int i = x; i>0; i -= (i&-i)) 
		tmp += bit[i];
	return tmp;
}
void divide(int head, int tail, int l, int r)//表示下标head到tail的操作的答案区间为[l,r] 
{
	if (head>tail) return;
	if (l == r)
	{
		for (int i = head; i <= tail; i++)
			if (q[i].tp == 3) ans[q[i].s] = l;
		return;
	}
	int mid = (l + r) >> 1;
	for (int i = head; i <= tail; i++)
	{
		if (q[i].tp == 1 && q[i].y <= mid) add(q[i].x, 1);
		else if (q[i].tp == 2 && q[i].y <= mid) add(q[i].x, -1);
		else if (q[i].tp == 3) tmp[i] = sum(q[i].y) - sum(q[i].x - 1);
	}
	for (int i = head; i <= tail; i++)
	{
		if (q[i].tp == 1 && q[i].y <= mid) add(q[i].x, -1);
		else if (q[i].tp == 2 && q[i].y <= mid) add(q[i].x, 1);
	}
	int l1 = 0, l2 = 0;
	for (int i = head; i <= tail; i++)
		if (q[i].tp == 3)
		{
			if (q[i].cur + tmp[i]>q[i].k - 1)//q[i].cur+tmp[i]表示累积了多少个数
				q1[++l1] = q[i];
			else
			{
				q[i].cur += tmp[i];
				q2[++l2] = q[i];
			}
		}
		else
		{
			if (q[i].y <= mid) q1[++l1] = q[i];
			else q2[++l2] = q[i];
		}
	for (int i = 1; i <= l1; i++) q[head + i - 1] = q1[i];
	for (int i = 1; i <= l2; i++) q[head + l1 + i - 1] = q2[i];
	divide(head, head + l1 - 1, l, mid);
	divide(head + l1, tail, mid + 1, r);
}
int main()
{
	int T;
	scanf("%d", &T);
	while (T--)
	{
		scanf("%d%d", &n, &m);
		init(); 
		for (int i = 1; i <= n; i++)
		{
			scanf("%d", &a[i]);
			q[++num].x = i; q[num].y = a[i];
			q[num].tp = 1; q[num].s = 0;
		}
		char op[10];
		int x, y, z;
		for (int i = 1; i <= m; i++)
		{
			scanf("%s", op);
			if (op[0] == 'Q')
			{
				scanf("%d%d%d", &x, &y, &z);
				q[++num].x = x, q[num].y = y, q[num].k = z;
				q[num].tp = 3; q[num].s = ++cnt;
			}
			else
			{
				scanf("%d%d", &x, &y);
				q[++num].x = x; q[num].y = a[x];
				q[num].tp = 2; q[num].s = 0;
				q[++num].x = x; q[num].y = y;
				q[num].tp = 1; q[num].s = 0;
				a[x] = y;
			}
		}
		divide(1, num, 0, inf);
		for (int i = 1; i <= cnt; i++)
			printf("%d\n", ans[i]);
	}
	return 0;
}


猜你喜欢

转载自blog.csdn.net/qq_22522375/article/details/51291602