/*
* Problem :: 15683 / 감시
*
* Kind :: Simulation
*
* Insight
* - 음... 한번에 사각지대를 최소화 하는 CCTV 의 방향들을 알 수 있는
* 알고리즘은 발견하기 힘들것 같다...
* 그냥 다 해볼 수 있나?
* + max(N)=8, max(M)=8
* CCTV 가 64개 있으면 4*64 인데
* 최대 CCTV 의 개수가 8개 이므로 4*8 이니
* 시간 제한 내에 풀 수 있을 것 같다
*
* - CCTV 의 방향을 어떻게 설정할 수 있을까?
* L = 왼쪽, R = 오른쪽, D = 아래쪽, U = 위쪽 이라고 하면...
* + 1번 CCTV : 4가지 종류의 방향 ([L], [R], [D], [U])
* 2번 CCTV : 2가지 종류의 방향 ([L,R], [D,U])
* 3번 CCTV : 4가지 종류의 방향 ([U,R], [R,D], [D,L], [L,U])
* 4번 CCTV : 4가지 종류의 방향 ([L,U,R], [U,R,D], [R,D,L], [D,L,U])
* 5번 CCTV : 1가지 종류의 방향 ([L,U,R,D])
* (톱니 배열... Vector 를 활용해서 넣어주자)
* # DFS 도는 것처럼 재귀로 그때 그때 CCTV 의 방향을 설정해 준 뒤,
* 모든 CCTV 의 방향을 설정해 주었으면 그때 사각지대의 개수를 구하자
*
* - 공통적인 작업이 필요하다
* + CCTV 의 위치와 방향이 주어졌을 때, 사무실의 각 칸의 감시상태를 갱신해야한다
* # CCTV 를 주어진 방향대로 설치했을 때, 각 칸의 감시상태 갱신하는 함수를 만들자
* # CCTV 를 제거했을 대, 각 칸의 감시상태를 갱신하는 함수를 만들자
*
* Point
* - 0 0 1
* 0 1 0
* 이라고 초기 사무실의 칸이 주어졌을 때,
* + (0,2) 에 위치한 1번 CCTV 가 아래쪽을 감시하고
* (1,1) 에 위치한 1번 CCTV 가 오른쪽을 감시하면
* 0 0 1
* 0 1 #
* 이 된다
* # 하지만 (1,1) CCTV 를 제거할 때,
* 0 0 1
* 0 1 0
* 으로 상태를 갱신하면 안된다. 왜냐면 (0,2) CCTV 는 그 칸을 계속 감시중이기 때문이다
* -> 즉, 같은 칸이 여러 CCTV 에 중복으로 감시당하는 것을 추적해야한다.
* 코드에서는 이러한 칸의 경우 -1 을 해줌으로써 해결하였다
* (이는 어떤 칸이 -3 인 경우는 3개의 CCTV 가 그 칸을 감시하는 것을 뜻한다)
*
* - CCTV 가 있는 칸은 감시당하는 상태로 간주됨을 유의하자
*/
//
// BOJ
// ver.C++
//
// Created by GGlifer
//
// Open Source
#include <iostream>
#include <vector>
using namespace std;
#define endl '\n'
// Set up : Global Variables
int N, M;
int office[8][8];
struct CCTV { int y, x, no; }; /* CCTV 의 y 좌표, x 좌표, 번호 */
vector<CCTV> cctvs; /* 사무실의 CCTV 의 정보가 담겨진 Vector */
int dy[4] = {+1, 0, 0, -1};
int dx[4] = {0, +1, -1, 0};
enum Direction { D, R, L, U }; /* 가독성을 높이기 위해 방향을 나타내는 열거형 정의 */
vector<vector<int>> dir[5+1]; /* dir[cctv.no] = cctv.no 에 맞는 방향 정보가 담겨져 있음 */
#define INF 987654321
// Set up : Functions Declaration
int f(int idx);
bool isValid(int y, int x);
void setUp(int y, int x, int d);
void allSet(int y, int x, const vector<int>& dirs);
void rollBack(int y, int x, int d);
void allBack(int y, int x, const vector<int>& dirs);
int main()
{
// Set up : I/O
ios::sync_with_stdio(false);
cin.tie(nullptr);
// Process
/* dir[cctv.no] = cctv.no 에 맞는 방향 정보를 넣어줌 */
dir[1] = { {R}, {D}, {L}, {U} };
dir[2] = { {L,R}, {D,U} };
dir[3] = { {U,R}, {R,D}, {D,L}, {L,U} };
dir[4] = { {L,U,R}, {U,R,D}, {R,D,L}, {D,L,U} };
dir[5] = { {L,U,R,D} };
// Set up : Input
cin >> N >> M;
for (int i=0; i<N; i++) {
for (int j=0; j<M; j++) {
cin >> office[i][j];
if (office[i][j] > 0 && office[i][j] < 6) {
/* CCTV 의 위치 및 번호 정보 수집 */
cctvs.push_back({i, j, office[i][j]});
}
}
}
// Process
int ans = f(0);
// Control : Output
cout << ans << endl;
}
// Helper Functions
int f(int idx)
/* CCTV 들의 가능한 모든 방향 조합을 탐색한 후,
* 최소 사각지대의 개수를 반환 */
{
/* 모든 CCTV 의 방향을 설정 하고 각 칸의 감시상태가 그에 맞게 갱신되었으면 */
if (idx == cctvs.size()) {
/* 사각지대의 크기를 구하고 그 값을 반환 */
int blind = 0;
for (int i=0; i<N; i++) {
for (int j=0; j<M; j++) {
/* 감시당하지 않는 칸은 그 값이 0임 */
if (office[i][j] == 0) {
blind++;
}
}
}
return blind;
}
int ret = INF; /* 최소 사각지대의 크기 */
CCTV cctv = cctvs[idx]; /* 현재 CCTV 정보 얻음 */
/* dir[3] = { {U,R}, {R,D}, {D,L}, {L,U} }; 의 경우
* {U,R}, {R,D}, {D,L}, {L,U} 가 순서대로 dirs 가 되어 나온다 */
for (const vector<int>& dirs : dir[cctv.no]) {
allSet(cctv.y, cctv.x, dirs); /* 주어진 방향 정보(dirs) 에 따라
* 감시카메라 설치 및 칸의 감시상태 갱신 */
ret = min(ret, f(idx+1)); /* 이때, 가능한 최소 사각지대의 크기를 갱신 */
allBack(cctv.y, cctv.x, dirs); /* 주어진 방향 정보(dirs) 에 따라
* 감시카메라 제거 및 칸의 감시상태 갱신 */
}
return ret;
}
bool isValid(int y, int x)
/* 주어진 좌표가 유효하면 true 를 반환, 그 외 false 를 반환 */
{
return y >= 0 && y < N && x >= 0 && x < M;
}
void setUp(int y, int x, int d)
/* CCTV 의 위치와 방향이 주어졌을 때,
* CCTV 를 주어진 방향대로 설치하여 그에 따른 각 칸의 감시상태를 갱신 */
{
int ny = y + dy[d];
int nx = x + dx[d];
/* 주어진 방향으로 한 칸 움직인 다음 좌표가 유효하고 벽이 아니라면 그 좌표로 이동 */
while (isValid(ny, nx) && office[ny][nx] != 6) {
/* 현재 좌표가 CCTV 가 설치된 칸이 아니라면 */
if (office[ny][nx] <= 0) {
/* 감시당하고 있음 (현재 CCTV 에) */
office[ny][nx]--;
}
/* 다음 좌표 갱신 */
ny += dy[d];
nx += dx[d];
}
}
void allSet(int y, int x, const vector<int>& dirs)
/* CCTV 의 위치와 방향들이 주어졌을 때,
* CCTV 를 주어진 방향들대로 설치하여 그에 따른 각 칸의 감시상태를 갱신 */
{
for (int d : dirs) {
setUp(y, x, d);
}
}
void rollBack(int y, int x, int d)
/* CCTV 의 위치와 방향이 주어졌을 때,
* CCTV 를 주어진 방향에서 제거하여 그에 따른 각 칸의 감시상태를 갱신 */
{
int ny = y + dy[d];
int nx = x + dx[d];
/* 주어진 방향으로 한 칸 움직인 다음 좌표가 유효하고 벽이 아니라면 그 좌표로 이동 */
while (isValid(ny, nx) && office[ny][nx] != 6) {
/* 현재 좌표가 감시당하는 칸이라면 */
if (office[ny][nx] < 0) {
/* 더이상 감시당하지 않음 (현재 CCTV 에는) */
office[ny][nx]++;
}
/* 다음 좌표 갱신 */
ny += dy[d];
nx += dx[d];
}
}
void allBack(int y, int x, const vector<int>& dirs)
/* CCTV 의 위치와 방향들이 주어졌을 때,
* CCTV 를 주어진 방향들에서 제거하여 그에 따른 각 칸의 감시상태를 갱신 */
{
for (int d : dirs) {
rollBack(y, x, d);
}
}