티스토리 뷰
문제
스도쿠는 18세기 스위스 수학자가 만든 '라틴 사각형'이랑 퍼즐에서 유래한 것으로 현재 많은 인기를 누리고 있다. 이 게임은 아래 그림과 같이 가로, 세로 각각 9개씩 총 81개의 작은 칸으로 이루어진 정사각형 판 위에서 이뤄지는데, 게임 시작 전 일부 칸에는 1부터 9까지의 숫자 중 하나가 쓰여 있다.
나머지 빈 칸을 채우는 방식은 다음과 같다.
- 각각의 가로줄과 세로줄에는 1부터 9까지의 숫자가 한 번씩만 나타나야 한다.
- 굵은 선으로 구분되어 있는 3x3 정사각형 안에도 1부터 9까지의 숫자가 한 번씩만 나타나야 한다.
위의 예의 경우, 첫째 줄에는 1을 제외한 나머지 2부터 9까지의 숫자들이 이미 나타나 있으므로 첫째 줄 빈칸에는 1이 들어가야 한다.
또한 위쪽 가운데 위치한 3x3 정사각형의 경우에는 3을 제외한 나머지 숫자들이 이미 쓰여있으므로 가운데 빈칸에는 3이 들어가야 한다.
이와 같이 빈 칸을 차례로 채워 가면 다음과 같은 최종 결과를 얻을 수 있다.
게임 시작 전 스도쿠 판에 쓰여 있는 숫자들의 정보가 주어질 때 모든 빈칸이 채워진 최종 모습을 출력하는 프로그램을 작성하시오.
입력
아홉 줄에 걸쳐 한 줄에 9개씩 게임 시작 전 스도쿠판 각 줄에 쓰여 있는 숫자가 한 칸씩 띄워서 차례로 주어진다. 스도쿠 판의 빈칸의 경우에는 0이 주어진다. 스도쿠 판을 규칙대로 채울 수 없는 경우의 입력은 주어지지 않는다.
출력
모든 빈 칸이 채워진 스도쿠 판의 최종 모습을 아홉 줄에 걸쳐 한 줄에 9개씩 한 칸씩 띄워서 출력한다.
스도쿠 판을 채우는 방법이 여럿인 경우는 그중 하나만을 출력한다.
제한
- baekjoon의 백트래킹 알고리즘으로 풀 수 있는 입력만 주어진다. 다음은 그 알고리즘의 수행 시간이다.
- C++14: 80ms
- Java: 292ms
- PyPy3: 1172ms
코드
#include <iostream>
#include <vector>
using namespace std;
int board[9][9];
bool check_r[9][10]; // check_r[i][j]는 i번 행에 j라는 숫자가 있는지
bool check_c[9][10]; // check_c[i][j]는 i번 열에 j라는 숫자가 있는지
bool check_box[9][10]; // check_box[i][j]는 i번 박스에 j라는 숫자가 있는지
bool done; // 정답을 찾았는지
// board[r][c]에 i라는 숫자를 넣거나 삭제함
void set_flag(int r, int c, int i, bool flag)
{
if (flag)
board[r][c] = i;
else
board[r][c] = 0;
check_r[r][i] = flag;
check_c[c][i] = flag;
check_box[r / 3 * 3 + c / 3][i] = flag;
}
void print()
{
int i, j;
for (i = 0; i < 9; i++)
{
for (j = 0; j < 9; j++)
{
cout << board[i][j] << " ";
}
cout << endl;
}
}
void dfs(int num)
{
int r, c, i;
if (done) // 이미 정답을 발견했다면 바로 리턴
return;
r = num / 9; // num번 칸의 행 번호
c = num % 9; // num번 칸의 열 번호
if (num == 81) // 모든 칸을 채웠다면 출력하고 리턴
{
done = true;
print();
return;
}
if (!board[r][c]) // 빈칸인 경우 1~9의 숫자 중 이 칸이 포함되는 행, 열, 박스에 없는 숫자를 넣음
{ // dfs로 가능한 경우인지 확인 후 다시 칸을 비움 (다른 경우를 확인하기 위해)
for (i = 1; i <= 9; i++)
{
if (!check_r[r][i] && !check_c[c][i] && !check_box[r / 3 * 3 + c / 3][i])
{
set_flag(r, c, i, true);
dfs(num + 1);
set_flag(r, c, i, false);
}
}
}
else // 이미 숫자가 있는 칸이라면 바로 다음 칸을 확인함
{
dfs(num + 1);
}
}
int main()
{
int i, j, num;
// 입력받을 때 check_ 배열에도 표시함
for (i = 0; i < 9; i++)
{
for (j = 0; j < 9; j++)
{
cin >> num;
board[i][j] = num;
check_r[i][num] = true;
check_c[j][num] = true;
check_box[i / 3 * 3 + j / 3][num] = true;
}
}
// 첫 번째 칸부터 확인
dfs(0);
return 0;
}
입력을 받을 때 check_ 배열에 각각의 행, 열, 3 X 3 박스에 해당 인덱스의 숫자가 있는지 표시한다. 3 X 3 박스의 번호는 i / 3 * 3 + j / 3의 식으로 구할 수 있다.
이후 첫 번째 칸부터 확인하며 빈칸인 경우 가능한 숫자를 대입해보는 방식을 통해 가능한 경우인지 확인한다. 만약 모든 칸이 올바르게 채워졌다면 board를 출력한다. 이때 done을 true로 설정하여 더 이상 탐색하지 않게 함으로써 최초에 발견한 하나의 답만 출력할 수 있다.
링크
'Coding Test > Baekjoon' 카테고리의 다른 글
[백준] 1012번 - 유기농배추 / C++ (0) | 2021.06.12 |
---|---|
[백준] 7576번 - 토마토 / C++ (0) | 2021.06.11 |
[백준] 1463번 - 1로 만들기 / C++ (0) | 2021.04.14 |
[백준] 15685번 - 드래곤 커브 / C++ (0) | 2021.04.14 |
[백준] 15686번 - 치킨 배달 / C++ (0) | 2021.04.14 |