S와 K
시간 제한3초메모리 제한128 MB
S와 K로 이루어진 이진 트리가 주어질 때 두 규칙을 더 이상 적용할 수 없을 때까지 반복 적용한 뒤 최종 트리 문자열을 출력한다.
문제
유용한 계산을 수행할 수 있는 가장 단순한 컴퓨터는 어떤 모습일까? 오랫동안 이론 전산학자들은 튜링 기계나 람다 대수처럼 여러 단순한 계산 모델을 고안해 왔다. 이 문제에서는 1924년 Moses Schönfinkel이 고안한, 단 두 글자 와 만으로 이루어진 더욱 단순한 체계를 살펴본다. 이 글자들을 구조화하기 위해 이진 트리로 배열하며, 트리의 모든 잎 노드는 또는 중 하나이다.
이러한 이진 트리는 다음과 같이 문자열로 인코딩한다. 잎 노드는 또는 로 쓴다. 잎이 아닌 내부 노드는 로 쓰며, 는 왼쪽 자식의 문자열, 는 오른쪽 자식의 문자열이다. 예를 들어, 어떤 트리는 문자열 로 표현된다.
트리는 아래 규칙을 반복 적용하여 변환한다. 매 단계마다 규칙을 주어진 순서대로, 즉 규칙 (1)을 먼저, 그다음 규칙 (2)를 시도하며, 어떤 규칙이 변환을 수행하자마자 전체 트리의 루트에서 다시 규칙 (1)부터 검사를 시작한다.
- 트리가 형태이고 , 가 임의의 부분 트리일 때, 전체를 부분 트리 로만 바꾼다.
- 트리가 형태이고 , , 가 임의의 부분 트리일 때, 로 바꾼다. 이는 와 를 각각 한 번씩, 를 두 번 포함한다.
- 위 두 규칙을 현재 노드에서 적용할 수 없으면, 현재 노드의 왼쪽 자식 부분 트리에 이 규칙들을 재귀적으로 적용한다.
- 그래도 적용할 수 없으면, 현재 노드의 오른쪽 자식 부분 트리에 이 규칙들을 재귀적으로 적용한다.
- 어느 곳에서도 규칙을 적용할 수 없으면, 결과 트리의 문자열 표현을 출력하고 멈춘다.
이 단순한 규칙들로 어떻게 계산을 하는가? 먼저 자연수의 표현을 정한다. 여러 방법이 가능하지만, Alonzo Church가 고안한 방식이 가장 널리 쓰인다. 0을 로, 다음 수 연산자를 로 정의한다. 그러면 자연수는 , , , … 와 같이 정의되며, 나 같은 기호를 우리가 정의한 부분 트리로 매번 치환한다. 따라서 수 는 다음 트리로 표현된다
이 트리에는 부분 트리 가 네 번 나타난 뒤 부분 트리 이 따른다. 이렇게 인코딩된 수들은 다음 부분 트리로 더할 수 있다
예를 들어 , , 를 각각의 부분 트리로 치환하여 트리 를 만들고 변환 규칙을 적용하면, 결국 수 을 나타내는 트리에 도달한다. 곱셈은 더 간단한 부분 트리 로 할 수 있다. 다만 같은 연산자가 만들어 내는 결과는 같은 방식으로 동작하더라도 위에서 정의한 수와 글자 그대로 같아 보이지 않을 수 있다. 이때 정규화 연산자
를 적용하면 동치인 수들을 같은 모습으로 만들 수 있다. 예를 들어 트리 에 변환 규칙을 적용하면 수 을 나타내는 트리가 된다.
조금 더 손을 보면 비교, 조건문, 재귀처럼 프로그래밍 언어에서 기대할 만한 다른 연산을 위한 트리도 만들어 더 복잡한 프로그램을 작성할 수 있다. 예를 들어 다음 트리는 팩토리얼을 계산한다:
\displaystyle \begin{align\*} ! &= ((((SS)K)((S(K((SS)(S((SS)K)))))K))((S(K(S((S((S((S((SK)K))(K(K(K((SK)K)))))) \\\ &\mathrel{\phantom=} (KK)))(K((S((S(KS))K))(K((SK)K))))))))((S(K(S((S(KS))K))))((S((S(KS))K)) \\\ &\mathrel{\phantom=} (K((S(K(S(K(S(K((S((SK)K))(K(K((SK)K))))))))))((S((S(KS))((S(K(S(KS)))) \\\ &\mathrel{\phantom=} ((S(K(S(KK))))((S((S(KS))K))(K((S((S(KS))((S(K(S(K((S((S(KS))((S(KK))((S(KS)) \\\ &\mathrel{\phantom=} ((S(K(S((SK)K))))K)))))(KK))))))((S((S(KS))K))(K((S((SK)K))(KK))))))) \\\ &\mathrel{\phantom=} (K((S((SK)K))(KK))))))))))(K(K((S((S((S(KS))((S(KK))((S(KS)) \\\ &\mathrel{\phantom=} ((S(K(S((SK)K))))K)))))(KK)))((SK)K))))))))))) \end{align\*}
이것을 정규화 연산자 및 수 와 결합하여 트리 에 변환 규칙을 적용하면, (즉 )를 나타내는 트리가 된다.
입력
입력은 트리를 나타내는 여러 문자열로 이루어지며, 한 줄에 트리 하나씩 주어진다. 입력 한 줄은 자를 넘지 않는다. 입력의 마지막 줄은 빈 줄이다.
출력
마지막 빈 줄을 제외한 각 입력 줄에 대해, 더 이상 변환이 불가능할 때까지(규칙 5) 주어진 트리에 변환 규칙을 반복 적용한 뒤, 결과 트리의 문자열 표현을 한 줄에 출력한다. 참고로 다음과 같은 일부 트리는
규칙을 영원히 적용할 수도 있으나, 그러한 트리는 테스트 데이터에 포함되지 않는다.