3월 31, 2024

[백준] 2110번 공유기 설치 문제 이분탐색으로 쉽게 풀어보기

1. 문제

www.acmicpc.net/problem/2110

문제는 위의 링크를 클릭하면 확인할 수 있다.


2. 풀이

이 문제 또한 이분탐색으로 해결할 수 있다. 공유기 사이의 거리를 이분탐색을 통해 구해보면서 c 개를 설치할 수 있는 거리가 어디인지를 찾아보는 것이다.

 

이분탐색의 경우에는 초기의 left 와 right 값을 잘 정하는 것이 중요한데 여기서는 left의 값은 1로 잡고 (가장 최소의 값이 거리 1이기 때문) right의 값은 집의 위치를 정렬한 다음에 가장 끝 집과 첫 집의 거리로 잡으면 된다. (이것이 거리의 가장 최댓값)

 

그런다음에 이분탐색을 진행하고,

 

만약 가장 인접한 두 공유기 사이의 거리를 통해 주어진 공유기 c개를 설치할 수 있다면 left의 값을 mid+1로 조정, 설치할 수 없다면 right의 값을 mid-1로 조정한다.

 

공유기를 설치할 수 있는지를 판단하는 함수를 하나 만들어주었다.

public static boolean check(int mid){
        int count=1; 
        int first=place[0];
        for(int location: place){
            if (location-first>=mid){
                count++;
                first=location;
            }
        }
        return (count>=k);
    }

여기서 count는 설치할 수 있는 공유기의 개수를 의미하고 기준이 되는 place 의 값을 변경하면서 공유기를 설치할 수 있는지 세어주었다. 만약 count의 값이 문제에서 주어진 c 값보다 크거나 같다면 true를 return 아니면 false를 return 해준다.

 


위의 check 함수를 이용한 이분탐색 부분의 코드는 아래와 같다.

int ans=1;  //거리 최소 1
        int left=1;
        int right=place[n-1]-place[0]; //최대 거리
        while(left<=right){
            int mid=(left+right)/2;
            if (check(mid)){
                ans=Math.max(ans, mid);
                left=mid+1;
            }
            else{
                right=mid-1;
            }
        }

여기서 이분탐색을 사용하기 위해서는 반드시 정렬이 된 상태여야 하기 때문에 문제에서 입력받은 place를 먼저 정렬한 뒤 사용해야 한다.


3. 코드

이를 활용한 전체 코드는 아래와 같다. 

import java.util.*;

public class Main{
    static int k;
    static int []place;
    public static boolean check(int mid){
        int count=1; 
        int first=place[0];
        for(int location: place){
            if (location-first>=mid){
                count++;
                first=location;
            }
        }
        return (count>=k);
    }
    public static void main(String[] args){
        Scanner sc=new Scanner(System.in);
        int n=sc.nextInt();
        k=sc.nextInt();
        place=new int[n];
        for(int i=0; i<n; i++){
            place[i]=sc.nextInt();
        }
        Arrays.sort(place);
        int ans=1;  //거리 최소 1
        int left=1;
        int right=place[n-1]-place[0]; //최대 거리
        while(left<=right){
            int mid=(left+right)/2;
            if (check(mid)){
                ans=Math.max(ans, mid);
                left=mid+1;
            }
            else{
                right=mid-1;
            }
        }
        System.out.println(ans);
        
    }
}

 


이분탐색처럼 보이지 않는 문제도 이분탐색을 활용하면 쉽게 해결할 수 있는 경우가 많다. 위의 코드와 비슷한 문제 2805번 나무 자르기 문제도 그 예시중 하나다.


3월 31, 2024

[백준] 1654번 랜선 자르기 문제 이분탐색 활용해서 풀어보기

1. 문제

www.acmicpc.net/problem/1654


문제는 위의 링크에 들어가면 볼 수 있다.


2. 풀이

이 문제는 이분탐색을 활용해서 해결할 수 있는 문제이다. 

사실 모든 랜선의 길이를 하나씩 다 구해보는 방법도 있겠지만 그러면 시간이 오래걸리기 때문에 이분탐색으로 해서 시간을 줄이는 방법을 사용하면 된다.

 

처음에 left의 값은 1로 시작하고 right의 값은 랜선 길이 중에서 최대길이로 시작을 한다. ok라는 함수를 만들어서 랜선의 길이가 문제에서 주어진 개수보다 많거나 같으면 true를 return하고 작으면 false를 return한다.

public static boolean ok(long mid){
        int cnt=0;
        for(int i=0; i<a.length;i++){
            cnt+=(a[i]/mid);
        }
        return (cnt>=k);
    }

 

만약 return 값이 true라면 숫자를 증가시켜도 된다는 뜻이므로 left의 값을 mid+1로 조정하고, false라면 숫자를 낮추어야 한다는 뜻이므로 right를 mid-1로 조정한다. 

 

 

이를 반영한 이분탐색 부분의 코드는 아래와 같다.

 long ans=0;
        long left=1;
        long right=max;
        while(left<=right){
            long mid=(left+right)/2;
            if (ok(mid)){//원하는 개수 이상으로 만들 수 있음
                left=mid+1;
                ans=Math.max(ans, mid);
            }
            else{
                right=mid-1;
            }
        }

이렇게 이분탐색을 진행하면 빠르게 문제를 해결할 수 있다.

 

3. 코드 

전체 코드는 아래와 같다. 

import java.util.*;

public class Main{
    static int k;
    static long a[];
    public static boolean ok(long mid){
        int cnt=0;
        for(int i=0; i<a.length;i++){
            cnt+=(a[i]/mid);
        }
        return (cnt>=k);
    }
        
    public static void main(String[] args){
        Scanner sc=new Scanner(System.in);
        int n=sc.nextInt();
         k=sc.nextInt();
        a=new long[n];
        long max=0; //가장 길이가 긴 랜선 길이 저장
        for(int i=0; i<n; i++){
            a[i]=sc.nextInt();
            max=Math.max(max, a[i]);
        }
        long ans=0;
        long left=1;
        long right=max;
        while(left<=right){
            long mid=(left+right)/2;
            if (ok(mid)){//원하는 개수 이상으로 만들 수 있음
                left=mid+1;
                ans=Math.max(ans, mid);
            }
            else{
                right=mid-1;
            }
        }
        System.out.println(ans);
        
    }
}

3월 24, 2024

[백준] 10816번 숫자카드 2문제 풀어보기 (이분탐색 활용)

1. 문제

www.acmicpc.net/problem/10816

문제는 위의 백준 링크에 들어가면 확인할 수 있다.


2. 풀이

이 문자는 이분탐색을 이용하여서 쉽게 해결할 수 있다. 먼저 해당 숫자가 몇번 나오는지를 count해야 하기 때문에 두가지 함수를 만들어줄 것이다.

  1. lower_bound: 내가 찾는 숫자와 같은 수의 값을 가지고 있는 인덱스 중 가장 작은 인덱스를 return. 만약 없다면 -1 return
  2. upper_bound: 내가 찾는 숫자와 같은 수의 값을 가지고 있는 인덱스 중 가장 큰 인덱스를 return. 없다면 -1을 return

즉 정렬을 해준뒤에 lower_bound와 upper_bound의 값을 얻으면 해당 숫자가 몇개가 있는지 쉽게 구할 수 있다. 예를 들어, lower_bound의 return 값이 3, upper_bound의 return 값이 5라면, 3,4,5번 index에 해당 숫자가 있는 것이기 때문에 총 (5-3+1)개가 있다고 할 수 있다. 

 

즉 우리는 (upper_bound의 return 값 - lower_bound의 return 값 +1) 만큼 해당 숫자가 존재한다고 할 수 있다. 


여기서 upper_bound와 lower_bound 함수는 이분탐색을 사용하여 쉽게 구현할 수 있다. 

 

먼저 lower_bound부터 살펴보겠다.

static int lower_bound(int num){ //같은 수 중에서 가장 작은 인덱스
        int n=a.length;
        int left=0;
        int right=n-1;
        int ans=-1; //못찾았을경우에는 -1
        while(left<=right){
            int mid=(left+right)/2;
            if (a[mid]==num){
                ans=mid;
                right=mid-1;
            }
            else if (a[mid]>num){
                right=mid-1;
            }
            else{
                left=mid+1;
            }
        }
        return ans;
    }

원하는 숫자를 찾은 뒤에는 right을 mid-1의 값으로 바꾸어주어 while문을 끝나게 만들어주어야 한다.


upper_bound 함수도 코드 한 줄만 수정하여 쉽게 구현할 수 있다. 

static int upper_bound(int num){ //같은 수 중에서 가장 큰 인덱스 
        int n=a.length;
        int left=0;
        int right=n-1;
        int ans=-1; //못찾았을경우에는 -1
        while(left<=right){
            int mid=(left+right)/2;
            if (a[mid]==num){
                ans=mid;
                left=mid+1;
            }
            else if (a[mid]>num){
                right=mid-1;
            }
            else{
                left=mid+1;
            }
        }
        return ans;
    }

upper_bound는 해당 숫자를 찾은 뒤에도 해당 숫자를 값으로 가지고 있는 최대 index를 찾아야 하므로 left의 값을 mid+1으로 수정시켜 계속 while문을 돌도록 해야 한다.


3. 코드

이것을 코드로 구현하면 아래와 같다.

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

public class Main{
    static int a[];
    static int lower_bound(int num){ //같은 수 중에서 가장 작은 인덱스
        int n=a.length;
        int left=0;
        int right=n-1;
        int ans=-1; //못찾았을경우에는 -1
        while(left<=right){
            int mid=(left+right)/2;
            if (a[mid]==num){
                ans=mid;
                right=mid-1;
            }
            else if (a[mid]>num){
                right=mid-1;
            }
            else{
                left=mid+1;
            }
        }
        return ans;
    }
    static int upper_bound(int num){ //같은 수 중에서 가장 큰 인덱스 
        int n=a.length;
        int left=0;
        int right=n-1;
        int ans=-1; //못찾았을경우에는 -1
        while(left<=right){
            int mid=(left+right)/2;
            if (a[mid]==num){
                ans=mid;
                left=mid+1;
            }
            else if (a[mid]>num){
                right=mid-1;
            }
            else{
                left=mid+1;
            }
        }
        return ans;
    }
    public static void main(String[] args) throws IOException{
         BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.valueOf(br.readLine());
        String[] line = br.readLine().split(" ");
         a = new int[n];
        for (int i=0; i<n; i++) {
            a[i] = Integer.valueOf(line[i]);
        }
        Arrays.sort(a);
        int m = Integer.valueOf(br.readLine());
        String[] s = br.readLine().split(" ");
        StringBuilder ans = new StringBuilder();
        for(int i=0; i<m; i++){
            int num=Integer.valueOf(s[i]);
            int low=lower_bound(num);
            int high=upper_bound(num);
            if (low==-1){
                //없다는 뜻이므로
                ans.append("0 ");
            }
            else{
                ans.append((high-low+1)+" ");
            }
        }
        System.out.println(ans);
    }
}