Student. CSE

Query 정리

Query 정리

목차

제목 날짜
구간 질의(Range Query) 24.09.15
세그먼트 트리(Segment-Tree) 24.09.07

구간 질의

정적배열인경우

누적합

위의 누적합 배열을 사용할때, $[a,b]$에 속하는 원소들의 합을 $\text{sum}(a,b)$이라고 한다면

위치 k의 원소는 $\text{sum}(0,k)$이 되는데, 이떄 이 값들을 모두 미리 구해논다면 전처리를 $O(N)$에 수행할 수 있고, 질의에는 $O(1)$만 걸리게 된다.

Mo’s Algorithm

Mo’s Algorithm 을 이용하면 정적배열에서 Query를 정렬시켜 $O(Q+T(N) N \sqrt N)$ 안에 풀수 있게 해준다.

정적 배열이 아닌경우

원소갱신 업데이트가 있는 경우 이 작업을 수행하는데 로그 시간이 걸리는 자료구조를 사용한다.

Fenwick Tree

펜윅 트리를 구성할 때에는 1-based array를 사용한다. 또한 사용할 배열의 크기는 단순히 기존 배열의 크기와 동일하다.

$p(k)$는 k 의 약수중 $2^i$에 해당하는 수들에서 가장 큰 수로 정의한다면, 해당 자료구조에 저장할 수는 다음과 같다.

\[\text{tree}[k] = \text{sum}(k-p(k)+1,k)\]

위 수식을 풀어서 설명하면 배열 위치 $k$에 저장되는 값은 원래 배열에 대해 위치 $k$에서 끝나면서 그 길이가 $p(k)$인 구간의 합을 의미한다.

이 $p(k)$는 $k$를 binary로 나타내었을때, $k$의 비트 1 중에서 최하위 비트 한개만 남기는 것과 같은 의미이다.

그 값이 바로 $\text{tree}[k]$에서 저장될 누적합의 길이와 같다.

그리고 이는 $k \& -k$ 로 나타낼 수 있다.

const int SZ = 101010;
int tree[SZ];

int sum(int k){
	int s= 0;
	while(k>=1){
		s += tree[k];
		k -= k& -k;
	}
	return s;
}

void add(int k, int x){
	while(k <= SZ){
		tree[k] += x;
		k+= k& -k;
	}
}

다음처럼 Fenwick tree를 구현할 수 있다.

segment tree(구간트리)

세그먼트트리 또한 로그시간을 사용하는 트리를 이용한 자료구조이다. 누적합을 사용하는 펜윅 트리와는 다르게, 구간합을 제외한 range minimum , range maximum 등 다양한 질의를 수행할 수 있는 것이 장점이다.

세그먼트 트리의 구현은 이곳에서 참조하면 된다.

Sqrt Decomposition

Sqrt Dcomposition은 Segment tree 보다 성능은 좋지 못하지만 , Mo’s algorithm을 수행 할 때 필요한 알고리즘이다.

전처리에는 $O(\sqrt(N))$, 질의에는 $O(\sqrt(N))$ 이 걸린다.

ll arr[SZ], bucket[bSZ];
int p, n , m ,k;

void build(){
	p = sqrt(n);
	for(int i = 1; i<=n;i++){
		bucket[i/p] += arr[i];
	}
}

void update(int idx, ll k){
	arr[idx] = k;
	int bid = idx/p;
	bucket[bid] = 0;
	for(int i = bid * p; i<bid * p+p ;i++) bucket[bid] += arr[i];
}

ll query(int l, int r){
	ll ret = 0;

	while(l%p != 0 && l <= r) ret += arr[l++];
	while((r+1)%p != 0 && l <= r) ret += arr[r--];
	while(l <= r){
		ret += bucket[l/p];
		l += p;
	}
	return ret;
}