본문 바로가기

Dot Algo∙ DS/PS

[BOJ] 백준 8983번 사냥꾼 (Java)

    #8983 사냥꾼

    난이도 : 골드 4

    유형 : 이분 탐색

     

    8983번: 사냥꾼

    KOI 사냥터에는 N 마리의 동물들이 각각 특정한 위치에 살고 있다. 사냥터에 온 사냥꾼은 일직선 상에 위치한 M 개의 사대(총을 쏘는 장소)에서만 사격이 가능하다. 편의상, 일직선을 x-축이라 가

    www.acmicpc.net

    ▸ 문제

    KOI 사냥터에는 N 마리의 동물들이 각각 특정한 위치에 살고 있다. 사냥터에 온 사냥꾼은 일직선 상에 위치한 M 개의 사대(총을 쏘는 장소)에서만 사격이 가능하다. 편의상, 일직선을 x-축이라 가정하고, 사대의 위치 x1, x2, ..., xM은 x-좌표 값이라고 하자. 각 동물이 사는 위치는 (a1, b1), (a2, b2), ..., (aN, bN)과 같이 x,y-좌표 값으로 표시하자. 동물의 위치를 나타내는 모든 좌표 값은 양의 정수이다.

    사냥꾼이 가지고 있는 총의 사정거리가 L이라고 하면, 사냥꾼은 한 사대에서 거리가 L 보다 작거나 같은 위치의 동물들을 잡을 수 있다고 한다. 단, 사대의 위치 xi와 동물의 위치 (aj, bj) 간의 거리는 |xi-aj| + bj로 계산한다.

    예를 들어, 아래의 그림과 같은 사냥터를 생각해 보자. (사대는 작은 사각형으로, 동물의 위치는 작은 원으로 표시되어 있다.) 사정거리 L이 4라고 하면, 점선으로 표시된 영역은 왼쪽에서 세 번째 사대에서 사냥이 가능한 영역이다.

    사대의 위치와 동물들의 위치가 주어졌을 때, 잡을 수 있는 동물의 수를 출력하는 프로그램을 작성하시오.

     입력

    입력의 첫 줄에는 사대의 수 M (1 ≤ M ≤ 100,000), 동물의 수 N (1 ≤ N ≤ 100,000), 사정거리 L (1 ≤ L ≤ 1,000,000,000)이 빈칸을 사이에 두고 주어진다. 두 번째 줄에는 사대의 위치를 나타내는 M개의 x-좌표 값이 빈칸을 사이에 두고 양의 정수로 주어진다. 이후 N개의 각 줄에는 각 동물의 사는 위치를 나타내는 좌표 값이 x-좌표 값, y-좌표 값의 순서로 빈칸을 사이에 두고 양의 정수로 주어진다. 사대의 위치가 겹치는 경우는 없으며, 동물들의 위치가 겹치는 경우도 없다. 모든 좌표 값은 1,000,000,000보다 작거나 같은 양의 정수이다. 

     출력

    출력은 단 한 줄이며, 잡을 수 있는 동물의 수를 음수가 아닌 정수로 출력한다.

     

    문제 풀이  

    O(NM) = 10만*10만 = 100억이므로 선형탐색으로는 절대 풀 수 없다. 그래서 분할정복이나 이분탐색으로 탐색시간을 로그함수로 줄여줘야 한다. O(N*logM)으로 풀면 풀이가 가능하다. 그리고 L과 같이 최댓값이 10억이 넘으면 그냥 처음부터 Long으로 선언해주는 게 좋다.

     

    다음 코드가 O(N*M)코드이다. 이중 포문으로 구현하면 쉽지만 시간초과가 발생한다.  

    int cnt = 0;
    boolean[] check = new boolean[n];
    for(int i=0; i<n; i++) {
    	for(int j=0; j<m; j++) {
    		if(check[i]) continue;
    		
    		long dis = Math.abs(tower[j]-animal[i][0]) + animal[i][1];
    		if(dis <= l) {
    			check[i] = true;
    			cnt++;
    		}
    	}
    }
    System.out.println(cnt);

     

    이를 해결하려면 tower 탐색을 이분탐색으로 바꿔줘야 한다. 동물과 타워의 거리는 자세히보면 단순 합차연산이기 때문에 x와의 거리가 서로 근접해 있을 수록 더 짧은 거리가 됨을 알 수 있다.

    • 따라서 만약 dis > l 일 경우, tower를 현재 동물과 더 근접해있는 타워로 이분탐색을 해주면 된다.
    • if(animal[i][0] < tower[mid]) s = mid+1; // 타워 x가 동물 x보다 더 크면 감소시킨다.
    • else e = mid-1;  // 타워 x가 동물 x보다 더 작으면 증가시킨다.
    • 그리고 이분 탐색을 하려면 정렬은 필수이다. 정렬된 데이터만 이분 탐색이 가능하다.
    Arrays.sort(tower);
    int cnt =0;
    for(int i=0; i<n; i++) {
    	int s =0;
    	int e =m;
    	while(s<=e) {
    		int mid = (s+e)/2;
    		long dis = Math.abs(tower[mid]-animal[i][0]) + animal[i][1];
    		
    		if(dis <= l) {
    			cnt++;
    			break;
    		}
    		
    		if(animal[i][0] < tower[mid]) {
    			e = mid-1;
    		}else {
    			s = mid+1;
    		}
    	}
    	
    }
    System.out.println(cnt);

     

    풀이 코드 

    import java.io.*;
    import java.util.*;
    
    public class Main {
    
    	public static void main(String[] args) throws  IOException{
    		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    		StringTokenizer st = new StringTokenizer(br.readLine());
    		
    		int m = Integer.parseInt(st.nextToken());
    		int n = Integer.parseInt(st.nextToken());
    		long l = Long.parseLong(st.nextToken());
    		
    		int[] tower = new int[m];
    		st = new StringTokenizer(br.readLine());
    		for(int i=0; i<m; i++) {
    			tower[i] = Integer.parseInt(st.nextToken());
    		}
    		
    		int[][] animal = new int[n][2];
    		for(int i=0; i<n; i++) {
    			st = new StringTokenizer(br.readLine());
    			animal[i][0] = Integer.parseInt(st.nextToken());
    			animal[i][1] = Integer.parseInt(st.nextToken());
    		}
    		
    		Arrays.sort(tower);
    		int cnt =0;
    		for(int i=0; i<n; i++) {
    			int s =0;
    			int e =m-1;
    			while(s<=e) {
    				int mid = (s+e)/2;
    				long dis = Math.abs(tower[mid]-animal[i][0]) + animal[i][1];
    				
    				if(dis <= l) {
    					cnt++;
    					break;
    				}
    				
    				if(animal[i][0] < tower[mid]) {
    					e = mid-1;
    				}else {
    					s = mid+1;
    				}
    			}
    		}
    		System.out.println(cnt);
    	}
    }