2580 스도쿠
2580 스도쿠 (c++)
문제
https://www.acmicpc.net/problem/2580
접근 방식
백 트래킹
- 0이 적힌 칸이 없을 때 까지 다음을 반복
- 0이 적힌 칸에 1 ~ 9 중 숫자 하나를 집어 넣는다
- 0이 적힌 칸이 없으면 맞게 적어넣었는지 체크한다.
- 틀렸으면 한칸 전 0으로 돌아가서 다음 수를 넣어본다.
문제 풀이
#include <iostream>
#define endl '\n'
using namespace std;
int board[9][9], zeroRow[81], zeroCol[81], zeroCnt;
bool Used[3][9][10] = {};
bool Solve(int n);
void Print();
int main()
{
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
for (int i = 0; i < 9; ++i)
{
for (int j = 0; j < 9; ++j)
{
cin >> board[i][j];
if (!board[i][j])
{
zeroRow[zeroCnt] = i;
zeroCol[zeroCnt++] = j;
}
Used[0][i][board[i][j]] = Used[1][j][board[i][j]] = Used[2][(i/3) * 3 + (j / 3)][board[i][j]] = true;
}
}
Solve(0);
return 0;
}
bool Solve(int n)
{
if (n == zeroCnt)
{
Print();
return true;
}
int r = zeroRow[n];
int c = zeroCol[n];
for (int i = 1; i < 10; ++i)
{
if (!(Used[0][r][i] + Used[1][c][i] + Used[2][(r/3) * 3 + (c / 3)][i]))
{
Used[0][r][i] = Used[1][c][i] = Used[2][(r / 3) * 3 + (c / 3)][i] = true;
board[r][c] = i;
if (Solve(n + 1)) return true;
board[r][c] = 0;
Used[0][r][i] = Used[1][c][i] = Used[2][(r / 3) * 3 + (c / 3)][i] = false;
}
}
return false;
}
void Print()
{
for (int i = 0; i < 9; ++i)
{
for (int j = 0; j < 9; ++j) cout << board[i][j] << " ";
cout << endl;
}
}
다시 생각해 볼 점
- 처음엔 제한시간이 1초 (계산 1억회)이길래 조건 없이 0이 적힌 칸에 1 ~ 9 까지의 숫자를 하나씩 적어봤다. (시간 초과)
- 그 다음엔 아래의 배열에서 입력받은 숫자를 제외하고 나머지 숫자들 중 세 배열 모두 만족하는 수를 넣었다. (성공)
- 가로 배열 [9][9] : (첫 번째 숫자)의 행에 (두 번째 숫자)의 숫자가 존재하는지
- 세로 배열 [9][9] : (첫 번째 숫자)의 열에 (두 번째 숫자)의 숫자가 존재하는지
- 작은 사각형 배열 [9][9] : (첫 번째 숫자)의 작은 사각형에 (두 번째 숫자)의 숫자가 존재하는지
- 이 때 배열을 만들고 인덱스 계산을 잘못해서 시간을 많이 잡아먹었다.
- 작은 사각형 배열의 인덱스는 (i / 3) * 3 + (j / 3) 이다.
- 나누기 와 곱하기가 연달아 오길래 소거해버렸다. (멍청)
- 여기서 나누기의 의미는 나머지가 0~2까지의 수를 같은 수로 따지겠다는 의미이다. (0~2 = 0, 3~5 = 1, 6~8 = 2)
- 그리고 위의 알고리즘으로 풀면 결국 0의 갯수가 0이 되는 순간 문제가 해결된 것이나 다름이 없어서 Check를 할 필요가 없었다.
- 또한 중간의
if (Solve(n+1)) return true;
이 구절이 없으면 가능한 정답을 모두 출력하기 때문에 출력이 완료되면 빠져나오는 위의 구문이 중요했다.
- 마찬가지로 exit(0) 보다 return true 가 시간상으로도 효율적이었다. (exti(0)는 프로세스 강제 종료)
- 최적화된 다른 분의 코드를 참조했는데 여기서 새로운 사실을 깨달았다.
- Used라는 상기한 3가지 배열을 가지는 배열이 있는데 이를 수정할 때 = 으로 엮어서 한줄로 수정하는 것이 더 빠르다.
- 처음엔 0이 적힌 칸의 위치를 stack<pair<int, int»로 처리했었는데, 이러면 stack에 push, pop하는 시간이 걸린다.
- 이를 zeroRow[81], zeroCol[81] (0이 최대 81개이기 때문)으로 처리하면 배열 인덱스 n만 조절해주면 백트래킹해가며 0의 위치에 접근할 수 있다.
참조
https://www.acmicpc.net/source/38709329