3월 13, 2024

[백준] 15658번 연산자 끼워넣기 (2) 재귀로 풀어보기

1. 문제

1) 링크

www.acmicpc.net/problem/15658

2) 문제

N개의 수로 이루어진 수열 A1, A2, ..., AN이 주어진다. 또, 수와 수 사이에 끼워넣을 수 있는 연산자가 주어진다. 연산자는 덧셈(+), 뺄셈(-), 곱셈(×), 나눗셈(÷)으로만 이루어져 있다. 연산자의 개수는 N-1보다 많을 수도 있다. 모든 수의 사이에는 연산자를 한 개 끼워넣어야 하며, 주어진 연산자를 모두 사용하지 않고 모든 수의 사이에 연산자를 끼워넣을 수도 있다.

우리는 수와 수 사이에 연산자를 하나씩 넣어서, 수식을 하나 만들 수 있다. 이때, 주어진 수의 순서를 바꾸면 안 된다.

예를 들어, 6개의 수로 이루어진 수열이 1, 2, 3, 4, 5, 6이고, 주어진 연산자가 덧셈(+) 3개, 뺄셈(-) 2개, 곱셈(×) 1개, 나눗셈(÷) 1개인 경우에는 총 250가지의 식을 만들 수 있다. 예를 들어, 아래와 같은 식을 만들 수 있다.

  • 1+2+3-4×5÷6
  • 1÷2+3+4-5×6
  • 1+2÷3×4-5+6
  • 1÷2×3-4+5+6
  • 1+2+3+4-5-6
  • 1+2+3-4-5×6

식의 계산은 연산자 우선 순위를 무시하고 앞에서부터 진행해야 한다. 또, 나눗셈은 정수 나눗셈으로 몫만 취한다. 음수를 양수로 나눌 때는 C++14의 기준을 따른다. 즉, 양수로 바꾼 뒤 몫을 취하고, 그 몫을 음수로 바꾼 것과 같다. 이에 따라서, 위의 식 4개의 결과를 계산해보면 아래와 같다.

  • 1+2+3-4×5÷6 = 1
  • 1÷2+3+4-5×6 = 12
  • 1+2÷3×4-5+6 = 5
  • 1÷2×3-4+5+6 = 7
  • 1+2+3+4-5-6 = -1
  • 1+2+3-4-5×6 = -18

N개의 수와 연산자가 주어졌을 때, 만들 수 있는 식의 결과가 최대인 것과 최소인 것을 구하는 프로그램을 작성하시오.

3) 입력

첫째 줄에 수의 개수 N(2 ≤ N ≤ 11)가 주어진다. 둘째 줄에는 A1, A2, ..., AN이 주어진다. (1 ≤ Ai ≤ 100) 셋째 줄에는 합이 N-1보다 크거나 같고, 4N보다 작거나 같은 4개의 정수가 주어지는데, 차례대로 덧셈(+)의 개수, 뺄셈(-)의 개수, 곱셈(×)의 개수, 나눗셈(÷)의 개수이다. 

4) 출력

첫째 줄에 만들 수 있는 식의 결과의 최댓값을, 둘째 줄에는 최솟값을 출력한다. 연산자를 어떻게 끼워넣어도 항상 -10억보다 크거나 같고, 10억보다 작거나 같은 결과가 나오는 입력만 주어진다. 또한, 앞에서부터 계산했을 때, 중간에 계산되는 식의 결과도 항상 -10억보다 크거나 같고, 10억보다 작거나 같다.

 

더 자세한 문제의 제한을 보기 위해서는 위의 백준 링크에 들어가보자!

 


2. 풀이

이 문제는 연산자 끼워넣기 (1) 문제와 다른 점이 하나 있다. 바로 연산자가 N-1개 이상 주어진다는 것이다. 따라서 주어진 연산자를 모두 다 사용하는 것이 아니다.

 

만약 주어진 연산자를 다 사용하는 것이었다면 (연산자 끼워넣기(1) 문제) 해당 연산자로 순열을 만들어서 모든 경우의 수를 테스트해보아도 된다. 하지만 이렇게 연산자를 다 사용하지 않는 문제는 재귀를 통해서 풀면 더 효과적이겠다라고 생각했다.

 

재귀함수의 원형은 아래와 같다.

 static void algo(int index, int sum, int plus, int minus, int mul, int div)

여기서 index는 내가 현재 있는 index의 위치, sum은 지금까지 연산자를 사용했을 때의 결과, plus는 남은 사용가능한 +의 개수, minus: 남은 사용가능한 -의 개수, mul: 남은 사용가능한 *의 개수, div: 남은 사용가능한 / 의 개수를 뜻한다.

 

여기서 만약 각각의 연산자가 0보다 크다면 하나를 추가적으로 사용할 수 있는 것이기 때문에 

index를 하나 늘려주고, 결과값을 조정해주고, 해당 연산자의 수를 하나 감소시켜주면 된다.

 

이 재귀함수의 최종점은 index가 배열의 사이즈와 같을 때이다. 나는 결과값을 모두 다 ArrayList에다가 추가한 뒤에 나중에 max 와 min 값을 찾기로 했다.

 

이 재귀함수 부분을 구현한 코드는 아래와 같다. 

 

static void algo(int index, int sum, int plus, int minus, int mul, int div){
        if (index==a.length){
            result.add(sum);
            return;
        }
        if (plus>0){
            algo(index+1, sum+a[index], plus-1, minus, mul, div);
        }
        if (minus>0){
            algo(index+1, sum-a[index], plus, minus-1, mul, div);
        }
        if (mul>0){
            algo(index+1, sum*a[index], plus, minus, mul-1, div);
        }
        if (div>0){
            algo(index+1, sum/a[index], plus, minus, mul, div-1);
        }
    }

 

위에서 설명한 바와 같이 연산자를 무엇을 사용하느냐에 따라서 결과값을 조정해주고 연산자의 숫자를 하나 감소시켜준 것을 알 수 있다.

 


3. 코드

이를 바탕으로 전체 코드를 작성해보면, 위와 같다. 

import java.util.*;

public class Main{
    static int a[];
    static ArrayList<Integer> result=new ArrayList<>();
   
    static void algo(int index, int sum, int plus, int minus, int mul, int div){
        if (index==a.length){
            result.add(sum);
            return;
        }
        if (plus>0){
            algo(index+1, sum+a[index], plus-1, minus, mul, div);
        }
        if (minus>0){
            algo(index+1, sum-a[index], plus, minus-1, mul, div);
        }
        if (mul>0){
            algo(index+1, sum*a[index], plus, minus, mul-1, div);
        }
        if (div>0){
            algo(index+1, sum/a[index], plus, minus, mul, div-1);
        }
    }
    public static void main(String[] args){
        Scanner sc=new Scanner(System.in);
        int n=sc.nextInt();
        a=new int[n];
        for(int i=0; i<n; i++){
            a[i]=sc.nextInt();
        }
        int plus=sc.nextInt();
        int minus=sc.nextInt();
        int mul=sc.nextInt();
        int div=sc.nextInt();
        algo(1,a[0], plus, minus, mul, div);
        System.out.println(Collections.max(result));
        System.out.println(Collections.min(result));
    }
}

 

나는 결과값이 나올 때마다 일일히 최대, 최솟값을 구해주기 싫어서 모두다 ArrayList에다가 넣어버리고

Collections.max() 와 Collections.min()을 사용해 최대, 최솟값을 구해주었다.


2월 14, 2024

[백준] 14501번 퇴사문제 재귀로 풀어보기

 www.acmicpc.net/problem/14501

상담원으로 일하고 있는 백준이는 퇴사를 하려고 한다.

오늘부터 N+1일째 되는 날 퇴사를 하기 위해서, 남은 N일 동안 최대한 많은 상담을 하려고 한다.

백준이는 비서에게 최대한 많은 상담을 잡으라고 부탁을 했고, 비서는 하루에 하나씩 서로 다른 사람의 상담을 잡아놓았다.

각각의 상담은 상담을 완료하는데 걸리는 기간 Ti와 상담을 했을 때 받을 수 있는 금액 Pi로 이루어져 있다.

N = 7인 경우에 다음과 같은 상담 일정표를 보자.

 1일                2일                  3일                 4일                  5일                 6일                   7일

3 5 1 1 2 4 2
10 20 10 20 15 40 200

1일에 잡혀있는 상담은 총 3일이 걸리며, 상담했을 때 받을 수 있는 금액은 10이다. 5일에 잡혀있는 상담은 총 2일이 걸리며, 받을 수 있는 금액은 15이다.

상담을 하는데 필요한 기간은 1일보다 클 수 있기 때문에, 모든 상담을 할 수는 없다. 예를 들어서 1일에 상담을 하게 되면, 2일, 3일에 있는 상담은 할 수 없게 된다. 2일에 있는 상담을 하게 되면, 3, 4, 5, 6일에 잡혀있는 상담은 할 수 없다.

또한, N+1일째에는 회사에 없기 때문에, 6, 7일에 있는 상담을 할 수 없다.

퇴사 전에 할 수 있는 상담의 최대 이익은 1일, 4일, 5일에 있는 상담을 하는 것이며, 이때의 이익은 10+20+15=45이다.

상담을 적절히 했을 때, 백준이가 얻을 수 있는 최대 수익을 구하는 프로그램을 작성하시오.

입력

첫째 줄에 N (1 ≤ N ≤ 15)이 주어진다.

둘째 줄부터 N개의 줄에 Ti와 Pi가 공백으로 구분되어서 주어지며, 1일부터 N일까지 순서대로 주어진다. (1 ≤ Ti ≤ 5, 1 ≤ Pi ≤ 1,000)

출력

첫째 줄에 백준이가 얻을 수 있는 최대 이익을 출력한다.

 

더 자세한 내용은 위의 링크를 클릭하여 확인하자.

 

이 문제 또한 재귀로 풀 수 있다. 

재귀 함수: go(day, sum)

경우를 나누어 생각해보자

  1. 정답이 되었을 때:

    day==n+1 일때 
    최대를 구하는 경우이기 때문에 sum이 ans보다 큰지를 확인해주는 과정이 한 번 더 필요하다


  2. 정답이 될 수 없는 경우:

    day>n+1 일때

    이 경우는 n+1일에 퇴사를 하고 싶은데 그렇지 못한 경우이기 때문에 return만 해주면 된다.


  3.  다음 재귀함수를 불러야 하는 경우:

    1) 상담을 한 경우:

        상담을 했으니, go(day+ t[day], sum+p[day]) 를 불러야 함
        상담을 하는데 t[day]만큼 소요되기 때문이다

    2) 상담을 하지 않은 경우:

        해당 날에 상담을 하지 않았으므로, go(day+1, sum) 을 호출하면 된다.
static void go(int day, int sum) {
        if (day == n+1) {
            if (ans < sum) ans = sum;
            return;
        }
        if (day > n+1) {
            return;
        }
        
        go(day+t[day], sum+p[day]);
        go(day+1, sum);
    }

재귀함수 부분만 코드로 구현해 보면 위와 같다.

 

전체 코드는 아래와 같다. 

import java.util.*;

public class Main { 
   
    static int[] t;
    static int[] p;
    static int n;
    static int ans = 0;
    static void go(int day, int sum) {
        if (day == n+1) {
            if (ans < sum) ans = sum;
            return;
        }
        if (day > n+1) {
            return;
        }
        
        go(day+t[day], sum+p[day]);
        go(day+1, sum);
    }
    public static void main(String args[]) {
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        t = new int[n+1];
        p = new int[n+1];
        for (int i=1; i<=n; i++) {
            t[i] = sc.nextInt();
            p[i] = sc.nextInt();
        }
        go(1, 0);
        System.out.println(ans);
    }
}

여기서 1일부터 시작하기 때문에 일부러 t, p의 배열을 n+1개로 만들었다. 

만약 n개로 만들었다면 index를 하나씩 줄여야 할 것이다.