인프런 커뮤니티 질문&답변

shhmun02님의 프로필 이미지
shhmun02

작성한 질문수

10주완성 C++ 코딩테스트 | 알고리즘 코딩테스트

7-K

7-K dp 초기화 관련 질문드립니다

해결된 질문

작성

·

142

0

http://boj.kr/dcbf9db61feb45aab2fcb7f9d5e81b39
저는 cnt를 0부터 시작해서 목표한 target 값 까지 도달한 경우로 코드를 작성하고 나머지 부분은 선생님께서 설명하신 논리와 똑같은 것 같은데 코드에서 주석부분에서 memset으로 초기화 시키고 for문의 memset을 주석 처리 하면 왜 동작을 안하는 지 모르겠습니다.
4 4 4

2 2

1 1

3 3

3 4

//0 2 4 2 0

위의 경우를 집어 넣으면 전부 0 0 0 0 0이 나옵니다. 선생님께서 한경우와 제 경우가 무엇이 달라서 이렇게 나오는 지 잘 모르겠어서 질문을 남깁니다

답변 1

0

큰돌님의 프로필 이미지
큰돌
지식공유자

안녕하세요 ㅎㅎ

    // memset(dp, -1, sizeof(dp));
    for (int i = 0; i <= c; i++) {
        target = i;
        memset(dp, -1, sizeof(dp));
        cout << go(1, 1, 0, 0) << ' ';
    }

여기서 상단의 memset을 하고 go를 하는 것.

for문마다 하지 않는 것과는 큰차이가 있습니다.

이렇게 생각하시면 쉽습니다.

저 go라는 함수는 "같은 함수가 아닙니다."

물론 같은 매개변수 1, 1, 0, 0으로 시작하지만

target이 변경되면서

    if (y == n && x == m) {
        if (cnt == target && game[y][x] == 0) { return 1;}
        if (cnt == target - 1 && game[y][x] > idx) { return 1;}
        return 0;
    }

이렇게 기저사례가 바뀌게 되며 해당 DP에도 "다른 값"이 들어갑니다.

즉, target이 다른 go함수인 것이죠.

문제 지문에 따르면.

오락실을 0개 방문했을 때부터, C개 방문했을 때 까지 경우의 수에 대한 DP를 각각 구해야 하는 것입니다.

그렇기 때문에 매번 memset을 해주면서 초기화를 해주어야 합니다. 만약 go함수가 같은 함수이고 누적된 값을 이용한다면 그럴 필요가 없지만요.



또 질문 있으시면 언제든지 질문 부탁드립니다.

좋은 수강평과 별점 5점은 제게 큰 힘이 됩니다. :)

감사합니다.

강사 큰돌 올림.


shhmun02님의 프로필 이미지
shhmun02
질문자

답변감사합니다 선생님께서 올려주신 코드를 보면

int main(){

fastIO();

memset(dp, -1, sizeof(dp));

cin >> n >> m >> c;

for(int i = 1; i <= c; i++){

cin >> y >> x;

a[y][x] = i;

}

for(int i = 0; i <= c; i++){

cout << go(1, 1, i, 0) << " ";

}

return 0;

}
위 코드에서 dp를 한번만 초기화 시키는데 선생님께서 올려주신 코드는 dp를 한번만 초기화 해도 되는 이유가 궁금합니다

큰돌님의 프로필 이미지
큰돌
지식공유자

안녕하세요 ㅎㅎ

크리스마스에 답변 다는 강사... 큰돌입니다. 하하..

크리스마스땐 또 알고리즘 아니겠습니까?

 

제가 설명을 좀 혼란스럽게 드린 것 같습니다 ㅠ 저답변은 이렇게 수정하도록 하겠습니다.

수강생님 코드 기준 : 매번 초기화를 해야 -> 왜? target 다르게 해서 go함수할 때 그 이전 go가 다음 go 에 잘못된 영향을 미침 -> 매번 초기화 안하면 DP가 올바르게 안쌓임.

 

수강생님과 저의 코드의 차이는

이부분에 있는데요.

  1. cnt + 1

    if (game[y][x] > idx)
        ret = (go(y + 1, x, game[y][x], cnt + 1) + go(y, x + 1, game[y][x], cnt + 1)) % divn;

이렇게 + 1을 하면서 증가를 시키며 재귀함수를 작동시키죠? 그러면서 DP를 쌓구요. bottom-up이죠?

그리고...

  1. 그 + 1은 마지막 기저사례에도 연관이 되어있구요. (target과 비교해서...)

    if (y == n && x == m) {
        if (cnt == target && game[y][x] == 0) { return 1;}
        if (cnt == target - 1 && game[y][x] > idx) { return 1;}
        return 0;
    }

 

수강생님 코드는

0을 만들기 위해 1 2 3 4 를 호출해버립니다.

이거는 디버깅을 하면 알 수 있는데요. (지금 보시면.. 1 1 1로 뜨는 것을 볼 수 있습니다. 0까지만 잘 되요.)

예제 1기준.

1 : 1 : 0 : 0
-1
2 : 1 : 0 : 0
-1
3 : 1 : 0 : 0
-1
3 : 2 : 2 : 1
-1
2 : 2 : 1 : 1
-1
3 : 2 : 2 : 2
-1
2 : 3 : 1 : 1
-1
1 : 2 : 0 : 0
-1
2 : 2 : 1 : 1
0
1 : 3 : 0 : 0
-1
2 : 3 : 0 : 0
-1
RET : 1
1 : 1 : 0 : 0
1
RET : 1
1 : 1 : 0 : 0
1
RET : 1

이렇게 보면 target = 0이니까 0까지만 확인할 것 같은데

    if (y == n && x == m) {
        if ((cnt == target && game[y][x] == 0) || (cnt == target - 1 && game[y][x] > idx))
            return 1;
        return 0;
    }

코드를 보면 n, m 까지 와서 return .. 하기 때문에

int go(int y, int x, int idx, int cnt) {

이 cnt라는 부분이 + 1 + 1... 을 어느정도 하다가 DP가 만들어지게 됩니다.

그렇게 되면

DP[0]을 만들 때 DP[2]를 만드는 격이고.

DP[2]를 만들 때 그 만들어진 DP[2]를 참조하게 되버립니다.

이 때 DP[0]은 문제가 없지만 여기서 만들어지는 DP[2]는 문제가 있습니다.

오락실 0개를 방문했을 때 만들어지는 DP[2]라 아예 이상한 DP라고 보면 됩니다.

 

물론 이게 독립적으로 memset을 해서 매번 초기화하면 상관없지만 ...

그렇지 않으면 문제가 생기는 것이죠.

 

제코드

제코드를 간단히 설명하면

0

1 0

2 1 0

3 2 1 0 ...

이렇게 됩니다. 0을 만들 때는 그 0에 한정되어 DP를 만듭니다. 1을 만들 때는 그 이전에 DP를 사용하나 1을 만들 때 2, 3, 4... 의 DP를 만들지는 않습니다.

 

디버깅 코드는 다음과 같습니다.

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 51;
const int MOD = 1000007;
int dp[MAXN][MAXN][MAXN][MAXN];
int game[MAXN][MAXN];
int n, m, c, target;

int go(int y, int x, int idx, int cnt) {
    if (y > n || x > m) return 0; // Corrected boundary conditions
    if (y == n && x == m) {
        if ((cnt == target && game[y][x] == 0) || (cnt == target - 1 && game[y][x] > idx))
            return 1;
        return 0;
    }
    int &ret = dp[y][x][idx][cnt];
    cout << y << " : " << x << " : " << idx << " : " << cnt << "\n";
    cout << ret << "\n";
    if (ret != -1) return ret;

    ret = 0;
    if (y < n && (game[y + 1][x] > idx || game[y + 1][x] == 0))
        ret = (ret + go(y + 1, x, max(idx, game[y + 1][x]), game[y + 1][x] > idx ? cnt + 1 : cnt)) % MOD;
    if (x < m && (game[y][x + 1] > idx || game[y][x + 1] == 0))
        ret = (ret + go(y, x + 1, max(idx, game[y][x + 1]), game[y][x + 1] > idx ? cnt + 1 : cnt)) % MOD;

    return ret;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(NULL); cout.tie(NULL);

    cin >> n >> m >> c;
    int x, y;
    for (int i = 1; i <= c; ++i) {
        cin >> y >> x;
        game[y][x] = i;
    }
 
    memset(dp, -1, sizeof(dp));
    for (int i = 0; i <= c; ++i) {
        target = i; 
        int ret = go(1, 1, 0, 0);
        cout << "RET : " << ret << '\n'; 
    }
    cout << endl;

    return 0;
}

즐거운 크리스마스 보내시길 바랍니다.



또 질문 있으시면 언제든지 질문 부탁드립니다.

좋은 수강평과 별점 5점은 제게 큰 힘이 됩니다. :)

감사합니다.

강사 큰돌 올림.


shhmun02님의 프로필 이미지
shhmun02

작성한 질문수

질문하기