더 이상 tistory 블로그를 운영하지 않습니다. glanceyes.github.io에서 새롭게 시작합니다.

새소식

알고리즘/BOJ 풀이

BOJ 백준 17501번 수식 트리

  • -

 

BOJ 백준 17501 수식 트리

 

문제: https://www.acmicpc.net/problem/17501

 

17501번: 수식 트리

수식 트리는 루트가 있는 이진 트리로 2N-1개의 노드가 있습니다. 1번부터 N번까지 노드는 피연산자 노드이며 다른 노드들은 연산자 노드이고 2N-1번 노드가 루트입니다. 연산자 노드는 항상 두 개

www.acmicpc.net

 

N개의 피연산자 노드와 N-1개 연산자 노드로 구성된 이진트리에서 피연산자 노드 정수 값을 원하는 만큼 서로 바꿨을 때 나올 수 있는 수식 트리의 최댓값을 구해야 한다.

 

 

 

문제에서는 노드에 있는 정수를 원하는 만큼 바꾸어서 수식 트리의 결과값이 최대가 되도록 한다고 서술했지만, 관점을 달리 바라보면 처음부터 트리를 구성할 때 원하는대로 노드의 정수값을 정해줘도 된다.

 

문제를 풀기 전에 우선 간단한 예시를 생각해 봤다. 하나의 부모와 두 자식으로 이루어진 간단한 트리에서 연산자 노드가 어떤지에 따라 피연산자 노드를 어떻게 구성해야 하는지를 살펴보자.

 

 

연산자 노드가 ‘+’이면 왼쪽, 오른쪽 자식 구분 없이 피연산자 노드에는 무조건 가능한 큰 수가 오면 수식 트리의 값이 최대가 된다. 반면에 연산자 노드가 ‘-‘이면 왼쪽 자식에는 가능한 큰 수, 오른쪽 자식에는 가능한 작은 수가 와야 된다.

 

 

 

그러면 조상의 영향을 받을 수 있는, 다시 말해 두 개 이상의 ‘-‘ 연산자의 영향을 받는 피연산자 노드에는 어떠한 값이 와야 되는지를 알아봐야 한다. 이를 위해 주어진 수식 트리를 괄호가 쓰일 수 있는 일반적인 연산식으로 변환하여 생각했다.

 

아래와 같은 수식 트리가 있다고 가정했을 때 이를 식으로 바꾸면 다음과 같다.

 

 

A + B - (C - (D - E))

 

그런데 괄호가 있는 모든 식은 괄호가 없는 식으로 변환이 가능하다. 그래서 위 식의 괄호를 모두 풀어주면 아래의 식이 나온다.

 

A + B - C + D - E

 

식의 괄호를 모두 풀어주면 이제 수식 트리의 최댓값을 구하기 쉬워진다. 괄호를 다 푼 연산식에서 ‘+’ 연산자 뒤에는 가능한 큰 수를, ‘-‘ 연산자 뒤에는 가능한 작은 수를 배치할수록 전체 계산값은 커진다. 여기서 괄호를 풀 때 각 피연산자가 ‘-‘를 짝수 번 만나면 ‘+’로 되는데, 이는 수식 트리에서 루트 노드부터 해당 피연산자 노드까지 ‘-‘ 연산자 노드의 오른쪽 자식으로의 이동을 짝수 번 거치면 결과적으로 ‘+’ 연산자가 된다는 말과 동치이다.

 

 

 

종합하여 정리하면 다음과 같다.

 

 

 

‘-‘의 오른쪽 자식으로의 이동을 홀수 번 탔을 때 나오는 피연산자 노드에는 가능한 작은 수를, 나머지는 모두 가능한 큰 수를 배치한다.

 

 

 

배치하는 과정에서 가능한 작은 수 또는 큰 수를 구하는 건 간단하다. 수식 노드 탐색 전에 피연산자 노드 값들만 리스트로 모아서 정렬한 후 두 개의 인덱스를 사용하여 배치할 때마다 각 인덱스를 1만큼 증가 또는 감소해주면 된다.

 

 

 

아래 예시는 이를 적용한 과정을 그림으로 도식화한 것이다. 간선에서의 ‘-‘ 오른쪽 자식으로의 이동을 의미한다. 피연산자 노드 C E 홀수 거치므로 (C 1, E 3) 각각 15, 20, 나머지에는 50, 40, 25 배치하면 수식 트리의 최댓값이 80으로 나온다.

 

 

 

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
using ll = long long;

const int N_MAX = (int)1e5;

int n, front, rear;
int oper[N_MAX * 2 + 1];
int leftChild[N_MAX * 2 + 1];
int rightChild[N_MAX * 2 + 1];
int parent[N_MAX * 2 + 1];
vector<ll> a;

ll getMaxResult(int now, int subCnt){
    ll ret = 0;
    // 연산자 노드이면
    if (now >= n + 1 && now < n * 2){
        // 현재 노드의 연산자가 '+'이면
        if (oper[now] == 1){
            // 왼쪽 자식
            ret += getMaxResult(leftChild[now], subCnt);
            // 오른쪽 자식
            ret += getMaxResult(rightChild[now], subCnt);
        }
        // 현재 노드의 연산자가 '-'이면
        else {
            // 왼쪽 자식
            ret += getMaxResult(leftChild[now], subCnt);
            // 오른쪽 자식
            ret -= getMaxResult(rightChild[now], subCnt + 1);
        }
    }
    // 피연산자 노드이면
    else {
        // 빼기 연산자가 홀수 번 작용하면
        if (subCnt % 2){
            ret = a[front++];
        }
        else {
            ret = a[rear--];
        }
    }
    return ret;
}

int main(){
    cin.tie(NULL);
    cin.sync_with_stdio(false);
    
    cin >> n;
    for (int i = 0; i < n; i++){
        ll x; cin >> x;
        a.push_back(x);
    }
    
    sort(a.begin(), a.end());
    front = 0; rear = (int)a.size() - 1;
    
    for (int i = n + 1; i < n * 2; i++){
        char o; int c1, c2;
        cin >> o >> c1 >> c2;
        oper[i] = (o == '+') ? 1 : 0;
        leftChild[i] = c1; rightChild[i] = c2;
        parent[c1] = parent[c2] = i;
    }
    
    ll res = getMaxResult(n * 2 - 1, 0);
    cout << res << "\n";
    
    return 0;
}
Contents

글 주소를 복사했습니다

부족한 글 끝까지 읽어주셔서 감사합니다.
보충할 내용이 있으면 언제든지 댓글 남겨주세요.