본문 바로가기

코딩테스트/백준

[백준/C++] 1074번. Z

문제

 

1074번: Z

한수는 크기가 2N × 2N인 2차원 배열을 Z모양으로 탐색하려고 한다. 예를 들어, 2×2배열을 왼쪽 위칸, 오른쪽 위칸, 왼쪽 아래칸, 오른쪽 아래칸 순서대로 방문하면 Z모양이다. N > 1인 경우, 배열을

www.acmicpc.net


1. 첫 번째 풀이 (마지막 풀이)

사고 과정

- 상당히 재귀적인 문제다.

- 먼저 4등분으로 나눈다. Z의 순서에 따라, 각각 1, 2, 3, 4평면이라고 하자.

- r행 c열은 넷 중 하나에 속하게 된다.

- 만약 크기가 4x4고(N = 2), r행 c열이 4평면에 있다고 하자. 탐색이 4평면에 도달하려면, 2x2 칸을 세 번 지나야 한다.

- 즉, 2^N 칸을 세 번 지나치므로, 12번 탐색부터 4평면에서 진행된다. (시작이 0이므로)

- 이를 모두 1칸 남을 때까지 반복하면 될 것 같다. base case를 N = 0으로 두고, 이땐 그냥 return 0로.

- 배열의 각 칸에 0, 1, 2, 3을 저장해두고, 이에 각각 2^i(i는 인덱스)를 곱한 후 모두 더하는 방법도 좋아보인다.

- 2진수같기도. 하지만 이왕 재귀로 시작한 거, 재귀로 끝을 보자.

 

1. N, r, c 입력

2. 재귀함수 작성, 매 재귀마다 n이 1씩 줄어들며, 어느 평면에 속하냐에 따라 count를 다르게 리턴

 

코드

#include <cstdio>
#include <cmath>

int prevSearchCount(int n, int r, int c) {
	if (n == 0) {
		return 0;
	}

	int half = std::pow(2, n - 1);

	if (c < half && r < half) {
		return prevSearchCount(n-1, r, c);
	}

	if (c >= half && r < half) {
		return half * half + prevSearchCount(n - 1, r, c - half);
	}

	if (c < half && r >= half) {
		return 2 * half * half + prevSearchCount(n - 1, r - half, c);
	}

	if (c >= half && r >= half) {
		return 3 * half * half + prevSearchCount(n - 1, r - half, c - half);
	}
}

int main() {
	int n, r, c;
	scanf("%d %d %d", &n, &r, &c);
	printf("%d", prevSearchCount(n, r, c));
}

가장 러프한 타입

 

결과

성공~


개선

사고 과정

- 알고리즘 측면에선 문제없어 보이고, 코드를 줄여보자.

- 우선 네 리턴값의 차이는, half * half에 몇을 곱하냐. 이건 따로 변수로 지정해줄 수 있겠고

- 재귀 매개 변수로 무엇을 넣느냐. r과 c를 그냥 감소시키면 되지 않을까?

- 결국 r과 c는 half 이상일 때 감소된다. 삼항 연산자로 if문을 통합할 수 있겠다.

- 두 변수 r, c를 half와 비교하여 4가지 중 하나를 고르는 건 통합이 어려울 것 같다.

 

코드

#include <cstdio>
#include <cmath>

int prevSearchCount(int n, int r, int c) {
	if (n == 0)	return 0;

	int half = std::pow(2, n - 1), m = 0;

	if (c >= half && r < half)	m = 1;
	if (c < half && r >= half)	m = 2;
	if (c >= half && r >= half)	m = 3;

	r -= (r < half) ? 0 : half;
	c -= (c < half) ? 0 : half;

	return m * half * half + prevSearchCount(n - 1, r, c);
}

int main() {
	int n, r, c;
	scanf("%d %d %d", &n, &r, &c);
	printf("%d", prevSearchCount(n, r, c));
}

1평면인 경우 그대로기에, 아예 삭제했다.

나는 중괄호를 살리는 편이지만, 코드 길이를 줄이기 위해 한 번 지워봤다.

 

결과

이번에도 문제 없음.


가장 효율적인 코드

https://www.acmicpc.net/source/31017326

cgiosy님의 코드. 정말 싫어하는 코드 타입이지만, 볼만한 부분이 있어 가져왔다.

 

내 코드와 비교

- 나는 재귀함수로, 이 분은 for문 반복문으로 구현하셨다. 정확히는 비트(2진수)를 이용하셨다.

- 문제의 n, r, c를 각각 N, y, x에 받으셨다.

- 정답은 z * 4 | x >> N & 1 | y * 2 >> N & 2;로 구하셨다. 비트 연산인데, 자세히 살펴보자.

- ex) N = 2, y = 3, x = 1일 경우

- y, x, 1, 2를 8비트로 풀어쓰면, 00000011, 00000001, 00000001, 00000010이다.

 

첫 번째 루프 (N = 2)

1. x를 2만큼 >> 시프트한다. (00000000)

2. 이를 1과 & 연산한다. (00000000 & 00000001 = 00000000)

3. y도 같은 방법으로 연산한다. y * 2 = 6, (00000110 >> 2 = 00000001, 00000001 & 00000010 = 00000000)

4. z는 처음 불려졌으니 0, z * 4 = 0.

5. 셋 모두 0이기에, 비트 OR 연산 ( | )을 해도 0이다.

 

두 번째 루프 (N = 1)

1. x를 1만큼 시프트한다. (00000000)

2. 이를 1과 & 연산한다. (00000000 & 00000001 = 00000000)

3. y도 같은 방법으로 연산한다. y * 2 = 6, (00000110 >> 1 = 00000011, 00000011 & 00000010 = 00000010)

4. z * 4 = 0.

5. y = 2로 살아남았다. | 연산을 하면 z = 2다. (00000010)

 

세 번째 루프 (N = 0)

1. x를 0만큼 시프트한다. (00000001)

2. 이를 1과 & 연산한다. (00000001 & 00000001 = 00000001)

3. y도 같은 방법으로 연산한다. y * 2 = 6, (00000110 >> 0  = 00000110, 00000110 & 00000010 = 00000010)

4. z * 4 = 8 (00001000)

5. 비트 OR 연산을 하면 z = 00001011, 즉 11이다.

 

비트 단위 연산이 들어가 어려워보이지만, 실제론 벡터로 생각하면 편하다. 각 반복(재귀)에서 사각형은 1/4로 줄어들며, 해당 사각형 내에서 또다시 연산한다. 2진수는 각 비트가 오른쪽 끝부터 2^0, 2^1, 2^2... 에 해당하므로, 이 문제를 풀기에 적합하다.

간단히 말하면, 내가 한 것처럼 이전까지 탐색한 수를 합쳐갈 뿐이다.


강평

문제 자체는 재귀의 정석이며 쉬운 문제. 하지만 비트 연산에 대한 이해를, 그리고 응용법을 배울 수 있었다.

'코딩테스트 > 백준' 카테고리의 다른 글

[백준/C++] 1083번. 소트  (1) 2024.01.01
[백준/C++] 1057번. 토너먼트  (0) 2023.09.04
[백준/C++] 1072번. 게임  (2) 2023.09.02
[백준/C++] 1092번. 배  (0) 2023.09.01
[백준/C++] 1027번. 고층 건물  (0) 2023.08.30