[Codeforces 2181M] Medical Parity (1700, DP)


문제 정보

  • 난이도 : 1700
  • 체감 난이도 : Gold 1
  • 알고리즘 : DP

문제 설명

0과 1로 이루어진 두 개의 이진 문자열 x, y가 주어진다.
y[i]는 (1번째 글자부터 i번째 글자까지의 문자열 x 내 1의 개수) % 2 를 의미한다.

예를 들어, 문자열 x = 11101, y = 10110에서,

  • y[1]x[1:2] = "1"에서 1이 한 개이므로, y[i] = 1 % 2 = 1이 된다.
  • y[2]x[1:3] = "11"에서 1이 두 개이므로, y[i] = 2 % 2 = 0이 된다.
  • y[3]x[1:4] = "111"에서 1이 세 개이므로, y[i] = 3 % 2 = 1이 된다.
  • y[4]x[1:5] = "1110"에서 1이 세 개이므로, y[i] = 3 % 2 = 1이 된다.
  • y[5]x[1:6] = "11101"에서 1이 네 개이므로, y[i] = 4 % 2 = 0이 된다.

하지만 비트가 뒤바뀐 채로 넘어오는 경우도 있다.
위 예시에서 x는 바뀌지 않았고, y의 세 번째 비트만 뒤바뀐 예시로, x' = 11101, y' = 10010x, y의 뒤바뀐 문자열 중 하나이다.

이 문제의 목표는 뒤바뀐 문자열 x', y'이 주어질 때, 이 문자열의 비트를 자유롭게 뒤바꿔서 올바른 x, y를 얻어내기 위해 최소 몇 번 비트를 바꿔야 하는지 구하는 것이다.

x' = 11101
y' = 10010

x = 11101
y = 10110

위와 같은 경우, 세 번째 비트가 잘못되었고, 이 비트만 바꾸면 올바른 x, y 문자열이 된다.

[원문]


문제 해결

위 아래로 두 개의 상태를 관리해야 하고, 이전 상태를 단순히 2차원 배열이 아닌, 조금 더 디테일하게 저장해야 하기 때문이다.
그래도 이 문제는 위 문제보다는 쉬워서 DP 점화식 세우는 것이 더 수월했다.

나는 DP 배열을 dp[i][a][b]로 세웠는데, 의미는 아래와 같다. (dp[N][2][2])

  • i번째 비트를 보고 있을 때, xi번째 글자가 a, yi번째 글자가 b인 경우, 바꾼 비트 개수의 최솟값

dp[i][0][0]은 이전 비트가

  • (x[i - 1], y[i - 1]) = (0, 0)
  • (x[i - 1], y[i - 1]) = (1, 0)
    둘 중 하나이어야 한다.

만약 (x[i - 1], y[i - 1]) = (0, 1)인 경우, y[i - 1]가 1이므로 i - 1번째까지 1이 홀수개 있었다는 의미인데, x[i]가 0이므로 1의 개수가 바뀌지 않아 y[i]y[i - 1]와 같은 값인 1이어야 하기 때문이다.

또한 같은 논리로, 만약 (x[i - 1], y[i - 1]) = (1, 1)인 경우, y[i - 1]가 1이므로 i - 1번째까지 1이 홀수개 있었다는 의미인데, x[i]가 0이므로 1의 개수가 바뀌지 않아 y[i]y[i - 1]와 같은 값인 1이어야 하기 때문이다.

비슷한 방식으로 나머지 상태도 업데이트 할 수 있다.

dp[i][0][1]은 이전 비트가

  • (x[i - 1], y[i - 1]) = (0, 1)
  • (x[i - 1], y[i - 1]) = (1, 1)
    둘 중 하나이어야 한다.

dp[i][1][0]은 이전 비트가

  • (x[i - 1], y[i - 1]) = (0, 1)
  • (x[i - 1], y[i - 1]) = (1, 1)
    둘 중 하나이어야 한다.

dp[i][1][1]은 이전 비트가

  • (x[i - 1], y[i - 1]) = (0, 0)
  • (x[i - 1], y[i - 1]) = (1, 0)
    둘 중 하나이어야 한다.

이전 상태를 참조할 때, 현재 상태에 맞게 비트 업데이트 횟수를 추가해줘야 한다.
예를 들어, (x'[i], y'[i]) = (1, 0)인데, (x[i], y[i]) = (0, 0)으로 업데이트한다고 하자.
그러면 x[i]는 뒤바뀌어야 하므로 1을 추가해주어야 한다.

업데이트가 끝난 후에는 min(dp[n][0][0], dp[n][1][0], dp[n][0][1], dp[n][1][1])을 출력해주면 된다.

시간 복잡도는 O(NT)이다. (T는 테스트케이스의 개수, N은 문자열의 길이)


정답 코드

Python
INF = int(1e9)

def check(a, b):
    cnt = 0
    for i in range(len(a)):
        if a[i] != b[i]:
            cnt += 1
    return cnt

def solve():
    x = " " + input()
    y = " " + input()

    n = len(x) - 1

    dp = [[[INF, INF], [INF, INF]] for _ in range(n + 1)]

    dp[1][0][0] = check("00", x[1] + y[1])
    dp[1][1][1] = check("11", x[1] + y[1])

    for i in range(2, n + 1):
        c = x[i] + y[i]

        dp[i][0][0] = min(
            dp[i][0][0],
            dp[i - 1][0][0] + check("00", c),
            dp[i - 1][1][0] + check("00", c),
        )
        dp[i][0][1] = min(
            dp[i][0][1],
            dp[i - 1][0][1] + check("01", c),
            dp[i - 1][1][1] + check("01", c),
        )
        dp[i][1][0] = min(
            dp[i][1][0],
            dp[i - 1][0][1] + check("10", c),
            dp[i - 1][1][1] + check("10", c),
        )
        dp[i][1][1] = min(
            dp[i][1][1],
            dp[i - 1][0][0] + check("11", c),
            dp[i - 1][1][0] + check("11", c),
        )
    print(min(dp[n][0] + dp[n][1]))

t = int(input())
for _ in range(t):
    solve()

마무리

경우의 수가 4가지이고, 각 상태마다 또 고려해야 하는 경우의 수가 2가지, 비트 비교하여 다른 개수만큼 더해줘야 하는 문제인데, 케이스 분류와 비트 비교 같은 것을 함수화하지 않으면 코드가 많이 복잡해졌을 것이라고 생각한다.

이 문제는 못 맞췄더라도 / 꼭 시도하지 않아도 DP 배열 설계나 상태 전이 점화식만 설계해봐도 좋을 거 같다.

댓글 남기기

Dalmeng's Footprints에서 더 알아보기

지금 구독하여 계속 읽고 전체 아카이브에 액세스하세요.

계속 읽기