본문 바로가기

Dot Algo∙ DS/PS

[BOJ] 백준 3653번 영화 수집 (Java)

#3653 영화 수집

난이도 : 플레 4

유형 : 펜윅 트리

 

3653번: 영화 수집

각 테스트 케이스에 대해서 한 줄에 m개의 정수를 출력해야 한다. i번째 출력하는 수는 i번째로 영화를 볼 때 그 영화의 위에 있었던 DVD의 개수이다. 상근이는 매번 영화를 볼 때마다 본 영화 DVD

www.acmicpc.net

▸ 문제

상근이는 영화 DVD 수집가이다. 상근이는 그의 DVD 콜렉션을 쌓아 보관한다.

보고 싶은 영화가 있을 때는, DVD의 위치를 찾은 다음 쌓아놓은 콜렉션이 무너지지 않게 조심스럽게 DVD를 뺀다. 영화를 다 본 이후에는 가장 위에 놓는다.

상근이는 DVD가 매우 많기 때문에, 영화의 위치를 찾는데 시간이 너무 오래 걸린다. 각 DVD의 위치는, 찾으려는 DVD의 위에 있는 영화의 개수만 알면 쉽게 구할 수 있다. 각 영화는 DVD 표지에 붙어있는 숫자로 쉽게 구별할 수 있다.

각 영화의 위치를 기록하는 프로그램을 작성하시오. 상근이가 영화를 한 편 볼 때마다 그 DVD의 위에 몇 개의 DVD가 있었는지를 구해야 한다.

 입력

첫째 줄에 테스트 케이스의 개수가 주어진다. 테스트 케이스의 개수는 100개를 넘지 않는다.

각 테스트 케이스의 첫째 줄에는 상근이가 가지고 있는 영화의 수 n과 보려고 하는 영화의 수 m이 주어진다. (1 ≤ n, m ≤ 100,000) 둘째 줄에는 보려고 하는 영화의 번호가 순서대로 주어진다.

영화의 번호는 1부터 n까지 이며, 가장 처음에 영화가 쌓여진 순서는 1부터 증가하는 순서이다. 가장 위에 있는 영화의 번호는 1이다. 

 출력

각 테스트 케이스에 대해서 한 줄에 m개의 정수를 출력해야 한다. i번째 출력하는 수는 i번째로 영화를 볼 때 그 영화의 위에 있었던 DVD의 개수이다. 상근이는 매번 영화를 볼 때마다 본 영화 DVD를 가장 위에 놓는다.

 

문제 풀이  

선형 탐색으로 풀이를 하면 시간초과가 발생하기 때문에 인덱스 트리나 펜윅 트리를 이용해 logN 쿼리를 사용하여 풀이해줘야 한다. 

 

특정 idx의 영화를 고른 후 다시 맨 위로 올려줘야 하는 로직은 트리 인덱스 크기를 n+m으로 초기화해준 다음 선택한 영화는 m크기의 공간으로 이동시키면 주면 된다.

  • 자신보다 위에 있는 비디오 수의 합을 구하는 문제이므로 M에 해당하는 부분을 인덱스 앞부분에 위치시킨다.
  • 예) 4번보다 위에 있는 비디오 (1,2,3)

 

영화 선택하기

 

시뮬레이션

두번째 예제의 데이터를 가지고 펜윅트리로 어떻게 동작하는지 간단하게 살펴보자. 

  • n = 5, m = 3

 

펜웍트리 초기화

앞에 m칸은 비워두고 뒤에 m+1~m+n칸까지 비디오를 초기화 시켜준다.

  • 4~8에 각각 +1을 더해준다.
  • 그리고 각 영화의 인덱스 부분 또한 다시 저장해줘야 한다. movieIdx[idx] = m+idx;

펜윅트리 초기화

 

4번 영화 꺼내기

4번 위에 쌓여있는 비디오의 수를 카운트해준 다음 m-idx칸으로 이동시킨다.

  • 4번 위에 쌓여있던 비디오는 총 3개이다. 
  • 그리고 7에 있던 4번 비디오를 3으로 이동한다. 

4번 비디오 뽑기

 

이런식으로 비디오 꺼내는 로직을 총 m번을 수행해주면 된다.

 

 

풀이 코드 

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {

	static int n, m;
	static int[] tree;
	
	static void update(int idx, int val)	{
		while(idx <=n+m) {
			tree[idx] += val;
			idx += (idx & -idx);
		}
	}
	
	static int sum(int idx) {
		int result = 0;
		while(idx > 0) {
			result += tree[idx];
			idx -= (idx& -idx);
		}
		return result;
	}
	
	public static void main(String[] args) throws IOException{
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		int tc = Integer.parseInt(br.readLine());
		StringBuilder sb = new StringBuilder();
		while(tc-- > 0) {
			StringTokenizer st = new StringTokenizer(br.readLine());
			n = Integer.parseInt(st.nextToken());
			m = Integer.parseInt(st.nextToken());
			int[] movieIdx = new int[n+1];
			tree = new int[n+m+1];
			
			for(int i=1; i<=n; i++) {
				movieIdx[i] = m+i;
				update(movieIdx[i], 1);
			}
			
			st = new StringTokenizer(br.readLine());
			for(int i=0; i<m; i++) {
				int select = Integer.parseInt(st.nextToken());
				int selectIdx = movieIdx[select];
				
				sb.append(sum(selectIdx-1)+" ");
				
				update(selectIdx, -1);
				movieIdx[select] = m-i;
				update(movieIdx[select], 1);
			}
			sb.append("\n");
		}
		System.out.println(sb.toString());
	}
}