본문 바로가기

Dot Algo∙ DS/PS

[BOJ] 백준 1849번 순열 (Java)

    #1849 순열

    난이도 : 플레 5

    유형 : 세그먼트 트리

     

    1849번: 순열

    1부터 N까지의 수들이 한 번씩 쓰인 수열이 있다. 그 수열에서 i 앞에 있는 수 들 중, i보다 큰 수들의 개수를 A[i]라고 정의하자. A[i]가 주어져 있을 때, 원래 수열을 구하는 프로그램을 작성하여라

    www.acmicpc.net

    ▸ 문제

    1부터 N까지의 수들이 한 번씩 쓰인 수열이 있다. 그 수열에서 i 앞에 있는 수 들 중, i보다 큰 수들의 개수를 A[i]라고 정의하자. A[i]가 주어져 있을 때, 원래 수열을 구하는 프로그램을 작성하여라.

     입력

    첫째 줄에 수열 원소의 개수 (1 ≤ N ≤100,000)이 주어진다. 그리고 두 번째 줄부터 N+1 번째 줄까지 차례로 A[i]가 주어진다.

     출력

    N개의 줄에 걸쳐서 수열을 출력한다. (i번째 줄에는 i번째 원소를 의미한다)

     

    문제 풀이  

    K번째 수를 찾는 유형과 비슷하다. 현재 N칸의 공간 중  앞에 있는 큰 수들을 개수만큼 이미 채워져있는 칸을 제외하고 건너뛰어서 위치를 지정해주면 된다. 예제는 다음과 같이 위치를 배정받게 된다.

    수 배정방식

     

     

    그런데 자신보다 큰수를 카운트하는 데 선형탐색으로 하면 O(n^2)으로 시간초과가 발생하기 떄문에 트리구조를 사용하여 로그시간으로 줄여줘야 한다. 그래서 세그먼트 트리를 사용하는 것이다. 만약 앞에 자신보다 큰 수가 k개있다고 할 때, 인덱스 트리로 다음과 같이 탐색할 수 있다.

    • tree[node*2] > k,  왼쪽 자식 트리가 k보다 크므로 자신보다 작은 값이 나올 때까지 계속 왼쪽 노드로 재귀탐색한다.
    • tree[node*2] < k, 왼쪽 자식 트리가 k보다 작으므로 자신보다 작은 개수를 빼주고 (cnt-tree[node*2]) 오른쪽 트리를 재귀 탐색한다.

    각 인덱스에 1을 채워놓아서 채워져있는지 비워있는지에 대한 표시를 해준다. 1이면 비어있는 것이다.

     

    세그먼트 트리 초기화

     

     

    1번 앞에 5개의 큰 수가 있을 경우 다음과 같이 탐색을 하게된다. 

     

     

     

    풀이 코드 

    import java.io.*;
    
    public class Main {
    
    	static int n;
    	static int[] tree;
    	public static void main(String[] args) throws IOException{
    		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    		int n = Integer.parseInt(br.readLine());
    		
    		tree = new int[4*n];
    		init(1,n,1);
    		
    		int[] res = new int[n+1];
    		for(int i=1; i<=n; i++) {
    			int x = query(1,n,1,Integer.parseInt(br.readLine()));
    			res[x] = i;
    			update(1,n,1,x);
    		}
    		
            StringBuilder sb = new StringBuilder();
    		for(int i=1; i<=n; i++) {
    			sb.append(res[i]+"\n");
    		}
    		System.out.println(sb.toString());
    	}
    	
    	static void init(int s, int e, int node) {
    		if(s == e) {
    			tree[node] = 1;
    			return;
    		}
    		
    		int mid = (s+e)/2;
    		init(s, mid, node*2);
    		init(mid+1, e, node*2+1);
    		tree[node] = tree[node*2] + tree[node*2+1]; 
    	}
    	
    	static void update(int s, int e, int node, int idx) {
    		if(idx < s || e < idx ) return;
    		if(s == e) {
    			tree[node] = 0;
    			return;
    		}
    		int mid = (s+e)/2;
    		update(s, mid, node*2, idx);
    		update(mid+1, e, node*2+1, idx);
    		tree[node] -= 1;
    	}
    	
    	static int query(int s, int e, int node, int val) {
    		if(s == e) return s;
    	
    		int mid = (s+e)/2;
    		if(tree[node*2] > val) {
    			return query(s, mid, node*2, val);
    		} else {
    			return query(mid+1, e, node*2+1, val - tree[node*2]);	
    		}
    	}	
    }