본문 바로가기

Dot Algo∙ DS/PS

[BOJ] 백준 13537번 수열과 쿼리1 (Java)

#13537 수열과 쿼리1

난이도 : 플레 3

유형 : 세그먼트 트리 / 머지소트 트리

 

13537번: 수열과 쿼리 1

길이가 N인 수열 A1, A2, ..., AN이 주어진다. 이때, 다음 쿼리를 수행하는 프로그램을 작성하시오. i j k: Ai, Ai+1, ..., Aj로 이루어진 부분 수열 중에서 k보다 큰 원소의 개수를 출력한다.

www.acmicpc.net

▸ 문제

길이가 N인 수열 A1, A2, ..., AN이 주어진다. 이때, 다음 쿼리를 수행하는 프로그램을 작성하시오.

  • i j k: Ai, Ai+1, ..., Aj로 이루어진 부분 수열 중에서 k보다 큰 원소의 개수를 출력한다.

 입력

첫째 줄에 수열의 크기 N (1 ≤ N ≤ 100,000)이 주어진다.

둘째 줄에는 A1, A2, ..., AN이 주어진다. (1 ≤ Ai ≤ 109)

셋째 줄에는 쿼리의 개수 M (1 ≤ M ≤ 100,000)이 주어진다.

넷째 줄부터 M개의 줄에는 쿼리 i, j, k가 한 줄에 하나씩 주어진다. (1 ≤ i ≤ j ≤ N, 1 ≤ k ≤ 109)

 출력

각각의 쿼리마다 정답을 한 줄에 하나씩 출력한다.

 

문제 풀이  

특정 구간의 k번째수, k보다 작은 수, 큰 수와 같은 문제는 머지소트 트리를 이용하여 풀이를 할 수 있다. 머지소트 트리는 세그먼트 트리의 형태를 지니고 있는 데 각 노드들이 리스트 구조를 있다는 점만 다르다.

 

예를 들어, [ 5 1 2 3 4 ]의 머지소트 트리를 만들면 다음과 같이 생성된다. 각 노드의 리스트들은 정렬을 시켜준 상태이다.

 

머지소트 트리 초기화

 

이제 여기서 각 구간에 들어가서 k보다 큰 원소의 개수(upperbound)를 구해주면 된다. 그렇게 구한 값을 리스트의 총 크기를 빼서 재귀로 쌓아준 다음 출력해주면 된다.

 

[2, 5] 구간에서 1보다 큰 원소의 개수 구하기

해당하는 구간에 가서 1보다 큰 원소의 개수를 각각 구해준다.

  • 2번 구간에 해당하는 원소 1은 1과 같으므로 0개이다.
  • 3번 구간에 해당하는 원소 2는 1보다 크므로 1개 존재한다.
  • [4,5]번 구간에 해당하는 원소는 3, 4로 둘 다 1보다 크므로 2개 존재한다.
  • 그래서 해당 쿼리는 총 3개를 출력해주면 된다

 

[2, 5] 구간에서 1보다 큰 원소의 개수 구하기

 

머지소트 트리를 생성하는 O(nlogn)이 걸리고, k보다 큰 수를 찾는 로직은 쿼리 로직안에 이분탐색도 존재하므로 O(m*log^2n)이 걸린다.

 

풀이 코드 

import java.io.*;
import java.util.*;

public class Main {
	static List<Integer>[] tree;
	static int[] arr;

	public static void main(String[] args) throws IOException{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		int n = Integer.parseInt(br.readLine());
		arr = new int[n+1];
		tree = new ArrayList[n*4];
		for(int i=0; i<n*4; i++) {
			tree[i] = new ArrayList<>();
		}
		
		StringTokenizer st = new StringTokenizer(br.readLine());
		for(int i=1; i<=n; i++) {
			arr[i] = Integer.parseInt(st.nextToken());
			update(1,n, 1, i);
		}
		
		for(int i=0; i<n*4; i++) {
			Collections.sort(tree[i]);
		}
		
		StringBuilder sb = new StringBuilder();
		int m = Integer.parseInt(br.readLine());
		for(int i=0; i<m; i++) {
			st = new StringTokenizer(br.readLine());
			int a = Integer.parseInt(st.nextToken());
			int b = Integer.parseInt(st.nextToken());
			int k = Integer.parseInt(st.nextToken());
			sb.append(getMoreThanNumOfK(1,n,1,a,b,k) +"\n");
		}
		
		System.out.println(sb.toString());
		
	}
	
	static void update(int s, int e, int node, int idx) {
		if(s > idx || e < idx ) return;	
		tree[node].add(arr[idx]);
		
		if(s == e) return;
		
		int mid = (s+e)/2;
		update(s, mid, node*2, idx);
		update(mid+1, e, node*2+1, idx);
	}
	
	static int getMoreThanNumOfK(int s, int e, int node, int l, int r, int k) {
		if(r < s || l > e ) return 0;
		if(l <= s && e <= r) {
			int x = upperbound(tree[node], k); // [s,e]에서 k보다 큰 갯수 
			return tree[node].size()-x;
		}
		
		int mid = (s+e)/2;
		return getMoreThanNumOfK(s, mid, node*2, l, r, k) + getMoreThanNumOfK(mid+1, e, node*2+1, l, r, k);
	}
	
	static int upperbound(List<Integer> data, int val) {
		int s = 0;
		int e = data.size();
		
		while(s < e){
			int mid = (s+e)/2;
			if(data.get(mid) <= val) {
				s = mid+1;
			}else {
				e = mid;
			}
		}
		return e;
	}
}