cubelover의 블로그

Baekjoon Online Judge 13538

...2016. 11. 3. 22:55

https://www.acmicpc.net/problem/13538


이 문제는 Persistent Segment Tree를 이용하면 효율적으로 해결할 수 있다.


수의 범위가 1~500000이므로 2^19(524288)개의 리프 노드를 만들고, 각 노드는 해당하는 구간에 포함되는 원소의 개수를 들고 있는다.


즉, k번째 Segment Tree의 [l, r) 노드는 1~k번째 원소 중 l 이상 r 미만인 수의 개수이다.


이렇게 트리를 만들면 각 쿼리는 다음과 같은 방법으로 처리할 수 있다.


0. 0번 트리를 만든다. 수열이 비어있으므로 모든 노드는 0을 값으로 갖는다.


1. 새로운 트리를 만든다. 이 트리는 현재까지의 수열에 x를 추가한 트리이고, 이를 N번 트리로 명명한다.


2. R번 트리와 L-1번 트리를 고려한다. 루트로부터 출발해서 x와 xor한 값이 더 큰 방향에 원소가 있으면 해당 방향으로, 없다면 반대 방향으로 움직이는 것을 19번 반복하면 y를 구할 수 있다.


3. N -= k


4. R번 트리와 L-1번 트리를 고려한다. 루트로부터 출발해서 x+1을 찾아가면서, 오른쪽 자식으로 갈 때 R번 트리에서 왼쪽 자식에 포함되는 원소의 개수를 더해주고 L-1번 트리에서 왼쪽 자식에 포함되는 원소의 개수를 빼 주는 것을 19번 반복하면 x보다 작거나 같은 원소의 개수를 구할 수 있다.


5. R번 트리와 L-1번 트리를 고려한다. 루트로부터 출발해서 R번 트리에서 왼쪽 자식에 포함되는 원소의 개수에서 L-1번 트리에서 왼쪽 자식에 포함되는 원소의 개수를 뺀 것이 k 이상이라면 왼쪽 자식으로 이동하고, 아니라면 그 값을 k에서 뺀 뒤 오른쪽 자식으로 이동한다. 이를 19번 반복해서 도착한 노드의 번호가 k번째로 작은 수이다.


전체 시간복잡도는 O(M log x)가 된다.

#include <cstdio>

struct node {
	node *l, *r;
	int x;
} T[11050000], *a[500005];
int Tn, n;

void Add(node *p, node *q, int x, int y) {
	p->x = q->x + 1;
	if (y < 0) return;
	if (x >> y & 1) {
		p->r = T + ++Tn;
		p->l = q->l;
		Add(p->r, q->r, x, y - 1);
	}
	else {
		p->l = T + ++Tn;
		p->r = q->r;
		Add(p->l, q->l, x, y - 1);
	}
}

int Xor(node *p, node *q, int x, int y) {
	if (y < 0) return 0;
	if (x >> y & 1 ? p->l->x == q->l->x : p->r->x != q->r->x) return 1 << y | Xor(p->r, q->r, x, y - 1);
	return Xor(p->l, q->l, x, y - 1);
}

int Sum(node *p, int x, int y) {
	if (y < 0) return 0;
	if (x >> y & 1) return p->l->x + Sum(p->r, x, y - 1);
	return Sum(p->l, x, y - 1);
}

int Kth(node *p, node *q, int x, int y) {
	if (y < 0) return 0;
	if (p->l->x - q->l->x < x) return 1 << y | Kth(p->r, q->r, x - p->l->x + q->l->x, y - 1);
	return Kth(p->l, q->l, x, y - 1);
}

int main() {
	int i, j, k, m;
	scanf("%d", &m);
	for (i = 1; i < 1 << 19; i++) {
		T[i].l = T + (i << 1);
		T[i].r = T + (i << 1 | 1);
	}
	a[n = 0] = T + 1;
	Tn = 1 << 20;
	while (m--) {
		scanf("%d", &i);
		switch (i) {
		case 1:
			scanf("%d", &i);
			a[++n] = T + ++Tn;
			Add(a[n], a[n - 1], i, 18);
			break;
		case 2:
			scanf("%d%d%d", &i, &j, &k);
			printf("%d\n", Xor(a[j], a[i - 1], k, 18));
			break;
		case 3:
			scanf("%d", &i);
			n -= i;
			break;
		case 4:
			scanf("%d%d%d", &i, &j, &k);
			printf("%d\n", Sum(a[j], k + 1, 18) - Sum(a[i - 1], k + 1, 18));
			break;
		case 5:
			scanf("%d%d%d", &i, &j, &k);
			printf("%d\n", Kth(a[j], a[i - 1], k, 18));
			break;
		}
	}
}


'...' 카테고리의 다른 글

ONTAK 2010 Day 7 Generator  (0) 2018.07.17
BOJ Achievements  (0) 2018.05.17
Ubuntu PPTP VPN Server  (0) 2017.12.17
Baekjoon Online Judge 1659  (0) 2016.02.02
1 이상 N 이하의 정수에서 i의 빈도수  (0) 2016.01.27

다음과 같은 문제를 생각해 보자.


"2차원 평면에 N개의 점이 있다. M개의 직사각형이 주어질 때, 각 직사각형 내부에 있는 점의 개수를 구하여라. 단 직사각형이 주어질 때마다 답을 구해야 한다."


모든 직사각형이 주어진 뒤에 답을 구하라고 한다면 점과 직사각형을 모두 정렬한 뒤 Segment Tree 등의 자료구조를 사용해서 O((N + M) log (N + M))으로 처리할 수 있지만, 이 문제에서는 직사각형이 주어질 때마다 답을 구해야 하기 때문에 쿼리를 정렬하는 방법은 사용할 수 없다.


한 가지 관찰할 수 있는 점은 (x1, y1)을 왼쪽 아래 꼭지점으로, (x2, y2)를 오른쪽 위 꼭지점으로 갖는 직사각형 내부의 점의 개수를 Q(x1, y1, x2, y2)라 하면, Q(x1, y1, x2, y2) = Q(x1, 0, x2, y2) - Q(x1, 0, x2, y1 - 1)라는 것이다. 따라서 Q(x1, 0, x2, y) 꼴의 쿼리를 효율적으로 처리할 수 있다면, 원래의 쿼리도 효율적으로 처리할 수 있다.


Segment Tree를 y좌표의 범위만큼 만들고, k번째 Segment Tree의 각 노드는 y좌표가 k 이하인 점들에 대하여 해당 구간에 포함되는 점의 개수를 들고 있으면 쿼리를 O(log N)으로 처리할 수 있다.


다만 이렇게 했을 경우의 문제점은 전처리에 O(N^2)의 시간이 필요하다는 것이다.


이를 위해 존재하는 자료구조가 Persistent Segment Tree이다.


k번째 Segment Tree가 다음처럼 생겼다고 해 보자.





y좌표가 k+1인 점들의 x좌표가 3과 5라고 해 보자. 그러면 k+1번째 Segment Tree는 다음처럼 생겼을 것이다.





이 때 관찰할 수 있는 점은, k+1번째 Segment Tree의 노드의 대부분은 k번째 Segment Tree의 노드와 같고, 바뀌는 노드 개수가 적다는 점이다.


이렇게 바뀌는 노드 개수는 추가되는 점마다 O(log N)개이므로 총 O(N log N)개이다.


따라서 모든 노드를 새로 만들지 말고 바뀌는 노드들만 새로 만든 뒤, 나머지 노드들은 포인터로 관리하여 이전 Segment Tree의 노드를 참조하도록 하면 O(N log N)의 시간 만에 똑같은 동작을 하는 자료구조를 만들 수 있다.


이를 Persistent Segment Tree라고 하며, 대부분의 자료구조가 갱신이 가능한 것과는 다르게 갱신이 불가능하다. 그러나 2차원 쿼리를 O(log N)에 처리할 수 있다는 장점이 있다.


이 자료구조는 예제를 보는 것이 좀 더 이해하기 좋으므로, 이 글을 참고하는 것을 권장한다. (스포일러 주의)

'자료구조' 카테고리의 다른 글

Splay Tree  (20) 2016.09.04