소수로만 이뤄진 마방진

요즘 사이트가 침체(?) 된거 같아 문제하나 투척하고 갑니다.^^

마방진은 각각의 숫자가 다르고 행과 열 및 대각선의 합이 같은 정사각 행렬이다.

가령 3x3 행렬의 마방진 중 대표적인 것은 다음과 같다.

4 9 2

3 5 7

8 1 6

그렇다면 3x3 마방진 중 500보다 작은 소수로만 이루어진 마방진을 몇개나 만들 수 있을까?

출력: 3x3 마방진 중 최대 500이하 소수로만 이뤄진 마방진의 갯수

단 1은 소수가 아니며, 회전하여서 같은 마방진은 1개로 생각한다.

※ 상대에게 상처를 주기보다 서로에게 도움이 될 수 있는 댓글을 달아 주세요.

1개의 풀이가 있습니다.

먼저 sieve와 primes를 초기화한 다음 찾아봅니다.

#define N 500

char sieve[N] = {0,};
int primes[N] = {0,};
int len = 0;

int main(int argc, const char * argv[]) {
    // sieve
    sieve[0] = -1;
    sieve[1] = -1;
    for (int i=2; i<N; i++) {
        if (sieve[i] == -1)
            continue;
        sieve[i] = 1;
        primes[len++] = i;
        for (int p=i*2; p<N; p += i) {
            sieve[p] = -1;
        }
    }

    int count = count_magic_squares(primes, len);
    printf("%d\n", count);

    return 0;
}

마방진 채우는 건 백트래킹을 사용하면 될것 같습니다.

// assume that nums[] are sorted
int count_magic_squares(int nums[], int len) {
    int count = 0;
    int square[9] = {0,};
    bt(square, 0, nums, len, &count);
    return count;
}

bt의 기본뼈대는 금방 만들어집니다. 한글자씩 채우면서 마방진 룰을 어기면 더이상 진행하지 않고 백트랙합니다.

void bt(int square[], int i, int nums[], int len, int* count) {
    if (i == 9) {
        ++(*count);
        return;
    }

    for (int k=0; k<len; k++) {
        if (nums[k]) { // not used
            square[i] = nums[k];
            nums[k] = 0; // use

            if (magic(square, i))
                bt(square, i+1, nums, len, count);

            nums[k] = square[i]; // recover
            square[i] = 0;
        }
    }
}

magic()은 채우진 숫자들로 마방진의 룰을 어기는지 확인합니다. (가로/세로/대각선 합이 같다) 여기서 조금이라도 빨리 룰 체크가 가능하도록 채우는 순서를 조금 바꿔봤습니다.

// 0 1 2
// 3 5 6
// 4 8 7

int magic(int s[], int i) {
    if (i < 3) return 1;
    int sum = s[0] + s[1] + s[2];

    if (i < 4) return 1;
    if (s[0] + s[3] + s[4] != sum) return 0;

    if (i < 5) return 1;
    if (s[2] + s[5] + s[4] != sum) return 0;

    if (i < 6) return 1;
    if (s[3] + s[5] + s[6] != sum) return 0;

    if (i < 7) return 1;
    if (s[0] + s[5] + s[7] != sum) return 0;
    if (s[2] + s[6] + s[7] != sum) return 0;

    if (i < 8) return 1;
    if (s[4] + s[8] + s[7] != sum) return 0;
    if (s[1] + s[5] + s[8] != sum) return 0;
    return 1;
}

이러면 .. 오래 걸려도 답은 나옵니다. 그런데.. 그 답은 90도씩 4번 회전 가능하고 거기다 거울상이 있으니 총 8번 중복되어 계산됩니다. 그냥 마지막에 8로 나눠줘도 되고 쉽게 기준 점을 정해서 0번이 2번과 4번보다 커야하고(회전 방지), 2번이 4번보다 커야 하고(거울상 방지)의 규칙을 추가하면 됩니다.

오래 걸리는 문제는.. 잘 모르겠네요. 일단 넣어보는 소수가 정렬되었다는 점을 이용해서 우선은 더 큰 값이 들어오는 걸 방지하는 것도 될 것 같아요. 그래서 룰을 어겼을 때 0을 반환하는 대신 더 큰 값은 시도하지 마라는 의미로 -1을 반환한다면 .. 조금 더 단축될 것 같습니다.

int magic(int s[], int i) {
    if (i < 2) return 1;
    if (s[0] < s[2]) return -1;    // 중복제거 및 s[2] 자리에 더 큰 값은 시도하지 마라 (-1)

    if (i < 3) return 1;
    int sum = s[0] + s[1] + s[2];  // 합을 구했음

    int s4 = sum - s[0] - s[3];    // s[4] 자리에 와야 하는 값이 정해짐
    if (s4 < 2) return -1;         // 만약 2보다 작다면 더 큰 값은 시도해볼 필요도 없음
    if (s4 > N || sieve[s4] != 1) return 0;  // 그 값이 소수 아니어도 더 볼 필요없음
    ...
}

이런 식으로 룰 체크를 조금 강화하니 조금 빨라지긴 하네요.

참, -1 반환 때문에 bt()도 바뀌었습니다.

void bt(int square[], int i, int nums[], int len, int* count) {
    if (i == 9) {
        //printsquare(square);
        ++(*count);
        return;
    }

    for (int k=0; k<len; k++) {
        if (nums[k]) { // not used
            square[i] = nums[k];
            nums[k] = 0; // use

            int result = magic(square, i);
            if (result == 1)
                bt(square, i+1, nums, len, count);

            nums[k] = square[i]; // recover
            square[i] = 0;

            if (result == -1) {  // no further search
                break;
            }
        }
    }
}
※ 상대에게 상처를 주기보다 서로에게 도움이 될 수 있는 댓글을 달아 주세요.

풀이 작성

※ 풀이작성 안내
  • 본문에 코드를 삽입할 경우 에디터 우측 상단의 "코드삽입" 버튼을 이용 해 주세요.
  • 마크다운 문법으로 본문을 작성 해 주세요.
  • 풀이를 읽는 사람들을 위하여 풀이에 대한 설명도 부탁드려요. (아이디어나 사용한 알고리즘 또는 참고한 자료등)
  • 작성한 풀이는 다른 사람(빨간띠 이상)에 의해서 내용이 개선될 수 있습니다.
목록으로
코딩도장

코딩도장은 프로그래밍 문제풀이를 통해서 코딩 실력을 수련(Practice)하는 곳입니다.


언어별 풀이 현황
전 체 x 2
기 타 x 1
python x 1