https://www.acmicpc.net/problem/1309

 

1309번: 동물원

첫째 줄에 우리의 크기 N(1≤N≤100,000)이 주어진다.

www.acmicpc.net

백준 1309번 동물원 문제를 풀던 도중...

d[n][k] 배열을 선언해야 했는데 (N(1≤N≤100,000), K = 3)

 

d = [[1, 0, 0]] + [[0, 0, 0]] * n 을 선언해서 solution을 돌렸을 때죽어도 원하는 값이 나오질 않았다.

 

분명 solution은 맞게 풀이하였고, 아무리 디버깅을 해봐도 맞았다.

 

그래서 d2 = [[0] * 3 for _ in range(n + 1)] 로 리스트를 다시 만들고d2[0][0] = 1로 초기화 해준 뒤 solution을 돌리니 원하는 값이 나왔다.

 

 

혹시나 해서 d의 값을 변경 해보았더니 아뿔싸.

얕은 복사가 이루어진 리스트 d

 

 

리스트 d를 선언할 때 [[0, 0, 0]] * n 이 문제였다.

리스트 속 리스트를 곱연산 하기 때문에 외부 리스트와는 별개로

내부의 [0, 0, 0]이 얕은 복사가 이루어져 d[0]를 제외한 d[1:n + 1]까지의 모든 리스트가 얕은 복사가 된 것이다.

 

얕은 / 깊은에 대해서 내가 의도적으로 사용하지 않는 한 의식해본 적이 없었는데,

이렇게 문제를 풀면서 내가 많은 것을 놓치고 있었다는걸 다시금 깨달았다.

 

분발하자!

'파이썬 (Python)' 카테고리의 다른 글

[22-03-06] 학습일지 "카운터, 열거형"  (0) 2022.03.06
20220131 공부일지  (0) 2022.01.31
22-01-10 학습일지 [비트 마스크]  (0) 2022.01.10
22-01-02 학습일지  (0) 2022.01.02
[파이썬] 21-12-03 학습일지  (0) 2021.12.03

비트 마스크 (Bitmask)

- 0 부터 N - 1 까지 정수로 이루어진 집합을 나타낼 때 사용

- 공간을 매우 절약할 수 있음

- 각종 연산을 조금 변형해서 사용해야 함

- 집합 값의 검사, 추가, 제거의 모든 복잡도가 O(1)임

 

A << B -> 비트를 왼쪽으로 한 칸 씩 미는 작업으로, A * 2^B과 같은 뜻

A >> B -> 비트를 오른쪽으로 한 칸 씩 미는 작업으로, A / 2^B과 같은 뜻

 

집합의 값을 검사, 추가, 제거 할 수 있음.

 

비트마스크 S에 X가 있는지 검사 ( AND 연산 )

- S & (1 << X) == 0 이면 없음, 0이 아니면(1<<X)이면 있음

 

비트마스크 S에 X를 추가 (OR 연산)

- 이미 있는 수를 또 추가할 때는 무시. (1 OR 1 = 1이기 때문)

-  S | (1 << X) 하는 것

 

비트 마스크 S에 X를 제거

- AND 연산일 경우 지우려는 값만 0으로 대조시키면 무조건 0으로

- S & ~(1 << X)

 

토글 연산

- 0 < - > 1 SWAP 연산

- S ^ (1 << X)

 

전체 집합

- (1 << N) - 1

 

공집합

- 0

 

비트 연산의 연산자 우선 순위를 잘 생각해야 한다.

- 비트 연산은 사칙연산보다 후순위 연산 순위를 가진다.

 

  • 정보·통신 생성 시스템에서, 문제의 해답을 구하기 위한 추론 제어 방식의 하나. 규칙을 적용하여 얻은 결과가 틀리면  규칙을 적용한 다음부터 현재까지의 결과를 무시하고 처음으로 돌아가서 다른 규칙을 선택하여 다시 시도한다.

-표준 국어 대사전에서 발췌-

 

 

일반적으로 재귀함수에서 더 이상의 호출이 의미 없을 때, 중간에 재귀함수를 중단시키는 알고리즘을 의미한다.

백준 14889번, 15661번 스타트 링크 문제를 통해 백트래킹에 대해 알아보자.

 

 

1 ~ N까지의 번호 중 2개의 팀을 각 N/2명으로 나누어야 한다.

2. 이때 S[I][J] = i번 사람과 j번 사람이 같은 팀일 때 팀에 더해지는 능력치이다.

3. 팀의 능력치 : 팀에 속한 모든 s[i][j]쌍의 합이다.

4. 문제 요구 사항 : 두 팀의 능력치를 구하고 차이의 최소를 구하라

 

해당 문제를 요구 사항에 맞게 코딩하면

 

def go(index, first, second):
    if index == n:
        if len(first) != n // 2:
            return -1
        if len(second) != n // 2:
            return -1
        t1 = 0
        t2 = 0
        for i in range(n // 2):
            for j in range(n // 2):
                if i == j:
                    continue
                t1 += s[first[i]][first[j]]
                t2 += s[second[i]][second[j]]
        diff = abs(t1 - t2)
        return diff
    ans = -1

    t1 = go(index + 1, first + [index], second)
    if ans == -1 or (t1 != -1 and ans > t1):
        ans = t1
    t2 = go(index + 1, first, second + [index])
    if ans == -1 or (t2 != -1 and ans > t2):
        ans = t2
    return ans


n = int(input())
s = [list(map(int, input().split())) for _ in range(n)]
print(go(0, [], []))

이런 코드가 나오게 된다.

 

index : 사람을 어떤 팀에 넣을지 (스타트 팀, 링크 팀)

first : 1번 팀, second : 2번 팀

index == n 일 경우 함수 종료

first나 second의 팀 수가 n//2를 넘어가게 되면(딱 절반이어야 하는데 넘어갈 때)  -1 반환

 

하지만 index == n 일때 

first나 second가 n//2가 아님을 검사하게 되면

결국 index는 n 만큼의 재귀를 실행하게 된다. 

만약 first나 second가 (n이 8일 때) 5를 넘었을 때 바로 종료한다면 더 시간을 단축할수 있을 것이다.

 

이때를 위해 백트래킹 방법이 사용된다.

재귀 중간에, 특정 상황(의미 없는 재귀가 반복된다고 판단)에서 재귀를 종료시키는 구문을 넣어준다.

 

def go(index, first, second):
    if index == n:
        if len(first) != n // 2:
            return -1
        if len(second) != n // 2:
            return -1
        t1 = 0
        t2 = 0
        for i in range(n // 2):
            for j in range(n // 2):
                if i == j:
                    continue
                t1 += s[first[i]][first[j]]
                t2 += s[second[i]][second[j]]
        diff = abs(t1 - t2)
        return diff
    ans = -1
    if len(first) > n // 2:
        return -1
    if len(second) > n // 2:
        return -1

    t1 = go(index + 1, first + [index], second)
    if ans == -1 or (t1 != -1 and ans > t1):
        ans = t1
    t2 = go(index + 1, first, second + [index])
    if ans == -1 or (t2 != -1 and ans > t2):
        ans = t2
    return ans


n = int(input())
s = [list(map(int, input().split())) for _ in range(n)]
print(go(0, [], []))

 

따라서 중간에

if len(first) > n // 2:
    return -1
if len(second) > n // 2:
    return -1

 

를 넘어줌으로써, 시간을 절약시킬 수 있다.

Row and column major order

 

row major order란?

- 행 우선 순위 탐색

1 2 3 4
5 6 7 8
9 10 11 12

다음과 같이 3*4 테이블이 있을 경우

array[3][4] 가 아닌 array[12]로 나타낼 수 있는데

만약 array[2][3]인 12를 출력하고 싶다면 array[i * 4 + j]를 출력해주면 된다

 

N, M 크기의 2차원 배열(리스트)를 1차원 배열로 (행) 전환

array[N][M] -> array[NM]

기존 2차원 배열의 (i, j)번째를 탐색하고 싶다면

array[NM]배열의 I * N + J 를 탐색하면 됨 

+ Recent posts