본문 바로가기
C++ 코딩 문제 풀이/백준

[Baekjoon] 동전 뒤집기

by 섬댕이 2023. 12. 9.

https://www.acmicpc.net/problem/1285

 

1285번: 동전 뒤집기

첫째 줄에 20이하의 자연수 N이 주어진다. 둘째 줄부터 N줄에 걸쳐 N개씩 동전들의 초기 상태가 주어진다. 각 줄에는 한 행에 놓인 N개의 동전의 상태가 왼쪽부터 차례대로 주어지는데, 앞면이 위

www.acmicpc.net

 


 

문제 해결 과정

착안

$N^2$ 개의 동전의 초기 상태로부터 뒷면(T)인 동전의 갯수가 최소가 되도록 행 또는 열을 뒤집는 경우를 찾기 위해서는 가능한 모든 경우를 확인해보아야 한다. 이때, 행이나 열을 뒤집는 동작을 수행할 때 선택된 행과 열 중에서 어느 것을 먼저 뒤집더라도 결과가 동일하므로, 편의상 항상 행을 먼저 다 뒤집은 다음에 열을 뒤집는 경우만 살펴볼 수 있다.

 

행 또는 열을 뒤집는 모든 경우의 수는 각각의 행과 열을 뒤집거나 뒤집지 않는 2 가지의 경우가 있으므로,

$$2^N \times 2^N = 2^{2N}$$

이다.

 

그러나, 문제에서 요구하는 것은 뒷면의 갯수가 최소가 되도록 하는 경우를 찾는 것이므로 실제로는 위와 같은 경우의 수를 모두 살펴볼 필요는 없다. 왜냐하면 뒤집을 행을 선택하여 모두 뒤집은 상황에서는, 특정 열을 뒤집었을 때 뒷면의 갯수가 이전보다 줄어드는 열만 선택하여 뒤집으면 뒷면의 갯수가 최소가 될 가능성이 있는 경우만 살펴보는 것이기 때문이다. 즉, 행을 선택하여 먼저 다 뒤집고 난 상태에서($2^N$ 가지의 경우), 뒷면의 갯수를 최대한 줄이도록 열을 뒤집는 방법의 수는 각각에 대해 1 가지 방법 밖에 없으므로 실제로는 $2^N$ 가지 경우만 살펴보면 되는 것이다.

 

따라서, 이 문제는 실질적으로 뒤집을 행을 선택하는 $2^N$ 가지의 경우를 살펴본 다음, 각각의 경우에 대해 뒷면의 갯수를 최대한 줄이는 방법 (각 경우에 대해 1 가지 방법만 존재)으로 열을 뒤집고 나서 뒷면의 갯수를 세어보는 문제가 된다.

 

구현

한 줄에 $N$ 개로 주어지는 동전의 앞, 뒷면을 각각의 한 줄씩 $N$ 개의 비트 형태로 표현(비트마스킹, bitmasking)하면 각각의 행을 하나의 숫자만으로 표현이 가능하며, 비트 NOT 연산자(~)를 사용해 행을 뒤집는 연산이 매우 간단해져 효율적인 문제 해결이 가능하다.

 

행을 뒤집는 경우는 $2^N$ 가지의 모든 경우를 전부 살펴보아야하므로 재귀 함수의 형태로 구현하였고, 각각의 열은 직접 뒤집을 필요 없이 $i \space (1 \le i \le N) $ 번째 열의 동전의 뒷면의 갯수 $T_i$ (뒤집지 않는 경우)를 계산해 $N - T_i$ (뒤집는 경우)와 비교하여 더 작은 값을 누적하여 합하는 방식으로 전체 동전의 뒷면의 갯수를 구해 문제를 해결하였다.

 

[스포 주의] 아래 '더보기'를 누르면 코드가 나오니 주의하세요~

더보기
#include <iostream>
#include <vector>

using namespace std;

int N;
int Min = 400;

int CountColumn(vector<int> rows, int col)
{
	int count = 0;
	for (int i = 0; i < N; i++)
		if (rows[i] >> col & 1)
			count++;

	if (count > N / 2)
		count = N - count;

	return count;
}

void FlipRow(vector<int> rows, int row)
{
	if (row < N)
	{
		FlipRow(rows, row + 1);
		rows[row] = ~rows[row];
		FlipRow(rows, row + 1);
	}

	int count = 0;
	for (int i = 0; i < N; i++)
		count += CountColumn(rows, i);

	if (Min > count)
		Min = count;
}

int main()
{
	cin >> N;
	vector<int> Rows(N);
	string str;
	for (int i = 0; i < N; i++)
	{
		cin >> str;
		int one = 1;
		for (char c : str)
		{
			if (c == 'T')
				Rows[i] += one;
			one <<= 1;
		}
	}

	FlipRow(Rows, 0);
	cout << Min << "\n";
	return 0;
}

 

실행 결과

'C++ 코딩 문제 풀이 > 백준' 카테고리의 다른 글

[Baekjoon] 11967번: 불켜기  (0) 2023.12.14
[Baekjoon] 2580번: 스도쿠  (0) 2023.12.11
[Baekjoon] 9084번: 동전  (1) 2023.12.07
[Baekjoon] 1063번: 킹  (2) 2023.12.05
[Baekjoon] 10830번: 행렬 제곱  (0) 2023.12.04

댓글