이번에는 백준 1285번 동전 뒤집기 문제를 풀어보았습니다.
처음에는 행과 열을 모두 뒤집는 경우를 완전탐색하려고 했습니다.
하지만 행과 열을 모두 고려하면 경우의 수가 최대 2^40이 되어 탐색이 불가능했습니다.
다시 생각해보니 행을 뒤집는 경우만 모두 탐색한 뒤, 열은 실제로 뒤집지 않더라도 현재 상태에서 최소 뒷면 개수를 바로 계산할 수 있다는 점을 이용하여 해결하였습니다.
이후 같은 로직을 비트마스킹으로 다시 구현해보았습니다.
각 행 또는 각 열을 선택하여 해당 줄의 모든 동전을 뒤집을 수 있습니다.
모든 작업이 끝났을 때 뒷면(T)의 개수가 최소가 되도록 해야 합니다.
최소가 되는 뒷면 개수를 구하는 문제입니다.
행을 뒤집는 모든 경우를 DFS로 탐색하였습니다.
행이 모두 결정된 이후에는 각 열을 확인하였습니다.
현재 열에 뒷면이 a개라면 열을 뒤집지 않는 경우는 a개, 뒤집는 경우는 N-a개가 됩니다.
따라서 각 열마다 min(a, N-a)를 더하면 해당 상태에서의 최소 뒷면 개수를 구할 수 있었습니다.
V1과 동일한 로직이지만 bool 배열 대신 비트마스킹을 이용하여 구현하였습니다.
각 행을 하나의 정수로 저장한 뒤 비트 연산으로 각 열의 상태를 확인하였습니다.
#include <bits/stdc++.h>
using namespace std;
bool bitmap[20][20];
int N;
int ret = INT_MAX;
int calculate() {
int cnt = 0;
for (int i = 0; i < N; i++) {
int t=0;
for (int j = 0; j < N; j++) {
if (bitmap[j][i]) t++;
}
cnt += min(t, N-t);
}
return cnt;
}
void flip(int row) {
for (int i = 0; i < N; i++) {
bitmap[row][i] = !bitmap[row][i];
}
}
void solve(int st) {
if (st == N) {
ret = min(ret, calculate());
return;
}
solve(st+1);
flip(st);
solve(st+1);
flip(st);
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
cin >> N;
for (int i=0; i<N; i++) {
string s;
cin >> s;
for (int j=0; j<N; j++) {
bitmap[i][j] = (s[j] == 'T');
}
}
solve(0);
cout << ret << '\n';
return 0;
}
현재 행의 모든 동전을 뒤집었습니다.
void flip(int row) {
for (int i = 0; i < N; i++) {
bitmap[row][i] = !bitmap[row][i];
}
}
DFS에서는 행을 뒤집는 경우와 뒤집지 않는 경우를 모두 탐색하였습니다.
모든 행이 결정되면 각 열의 뒷면 개수를 계산하였습니다.
int t = 0;
for (int j = 0; j < N; j++) {
if (bitmap[j][i]) t++;
}
현재 열에 뒷면이 t개라면
tN-t이므로
cnt += min(t, N-t);
를 이용하여 최소 개수를 계산하였습니다.
행만 완전탐색하면 모든 경우를 확인할 수 있습니다.
solve(st+1);
flip(st);
solve(st+1);
flip(st);
각 행마다 뒤집는 경우와 뒤집지 않는 경우를 모두 탐색하도록 구현하였습니다.
#include <bits/stdc++.h>
using namespace std;
int N;
int inp[21];
int ret = INT_MAX;
int calculate() {
int sum = 0;
for (int i=1; i<=(1 << (N-1)); i*=2) {
int cnt = 0;
for (int j=1; j<=N; j++) {
if (inp[j] & i) cnt++;
}
sum += min(cnt, N-cnt);
}
return sum;
}
void solve(int st) {
if (st == N+1) {
ret = min(ret, calculate());
return;
}
solve(st+1);
inp[st] = ~inp[st];
solve(st+1);
inp[st] = ~inp[st];
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
cin >> N;
for (int i = 1; i <= N; i++) {
string s;
cin >> s;
int tmp = 1;
for (int j=0; j<s.length(); j++) {
if (s[j] == 'T') inp[i] |= tmp;
tmp *= 2;
}
}
solve(1);
cout << ret << '\n';
return 0;
}
각 행을 하나의 정수로 저장하였습니다.
if (s[j] == 'T')
inp[i] |= tmp;
비트가 1이면 뒷면(T)을 의미하도록 하였습니다.
행을 뒤집을 때는 비트 NOT 연산을 이용하였습니다.
inp[st] = ~inp[st];
별도의 반복문 없이 한 번에 뒤집을 수 있었습니다.
각 열의 상태를 비트 연산으로 확인하였습니다.
if (inp[j] & i)
cnt++;
현재 열에서 뒷면의 개수를 계산하였습니다.
V1과 동일하게 각 열에서 뒤집는 경우와 뒤집지 않는 경우 중 작은 값을 선택하였습니다.
sum += min(cnt, N-cnt);
모든 열을 계산한 뒤 최소 뒷면 개수를 구하였습니다.