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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章