[수정][수정]JM book : ZIMBABWE 관련 문제 질문입니다.

  • canuyes
    canuyes

    아무래도 제 설명이 너무 성의 없는 것 같아 조금더 수정해서
    질문 올립니다.

    현재 ZIMBABWE문제를 풀고 있습니다.
    JM님의 책에 실린 풀이는 완성했고, 스스로의 풀이를 작성중입니다.
    랜덤으로 인풋을 던져보아도 답이 잘 출력됩니다.
    하지만,
    예제 입력의
    12738173912 7
    인풋에 대해 자꾸 틀린답을 내놓내요.

    분명 제 코드에 오류가 있으리라 생각합니다.
    좀 더 작은 사이즈의 문제에대한 오답 인/아풋풋을 알고싶습니다
    (rand()함수로 던져 보았는데, 모두 맞게 처리가 됩니다.)

    코드설명입니다.
    JM님의
    알고리즘문제해결전략(p.320~p.327)내용에 관련된것이고,
    아래부터는 그냥 JM book이라고 칭하겠습니다.

    일전에 JM님의 책에서 정수이외의 입력을 DP해야하는 경우,
    TSP2가 예로 들어진것을 보았습니다.
    TSP2에서는 현재도시 here과 정점의 방문정보 v(비트마스크)를 받아

    CACHE[here][v] :
    현재 here에 있고, 방문정보가 v로 주어질때, 남은 최소거리

    로 설정 하였습니다.

    저는 여기서 착안해서 ZIMBABWE 문제의 CACHE설정을 아래와 같이 하였습니다.

    CACHE[x][y] :
    숫자가 H[x]로 끝났고, 숫자들의 방문정보가 y로 주어질때,
    e보다 작거나 같으면서, m으로 나누어 떨어지는 숫자들의 갯수를 1000000007로 나눈 값.
    (H는 주어진 e를 오름차순으로 정렬한 string입니다.)

    코드를 간결하게 하기위해,
    저는 H에는 항상 '0'~'9' 보다항상 작은 ASCII값을 갖는 '-'를 포함시켰습니다.

    아래는 주석을 포함한, 코드입니다.

    #include<iostream>
    #include<cstring>
    #include<string>
    #include<algorithm>
    
    #define TWO16 65536
    #define REM 1000000007
    
    using namespace std;
    
    //'-'를 포함해서 총 16개의 방문정보를 다루게 되므로 16*2^16 크기의 배열을 만들게 됩니다.
    int M,CACHE[16][TWO16];
    
    //현재가격 e가 입력될 C와 C를 정렬하여 저장할 H입니다.
    string C,H;
    
    //x : H[x]로 끝난 숫자임을 의미함.
    //v : 비트마스크로 H의 방문정보가 저장됨.
    //ind : 이번에 채울 인덱스 (만들어질 숫자의)
    //r : JM book과 동일한 나머지 처리
    //l : 만들어진 숫자가 c보다 무조건 작다면 true, 아니라면 false
    int mkCACHE(int x,int v,int ind,int r,bool l){
        int i,&ret=CACHE[x][v];
        bool next;
        if(ret==-1){
            //모두 방문했다면, r이 0일때만 1, 아닐때는 0을 저장합니다.
            if(v==(1<<H.size())-1){
                ret=(r==0)? 1:0;
            }
            else{
                ret=0;
                //H[0]은 '-'이므로 i=1부터 순회합니다.
                for(i=1;i<H.size();i++){
                    //만들어진 숫자가 C보다 무조건 작지 않은 상태에서,
                    //C[ind]보다 H[i]가 더 커진다면 더 이상 비교를 수행할 필요가 없습니다.
                    //H는 오름차순으로 정렬되어있기 때문입니다.
                    if(!l&&C[ind]<H[i]){
                        break;
                    }
                    //H[i]가 미방문상태이고,
                    //H[i-1]!=H[i] || (v&(1<<(i-1)))!=0 이라면,(JM book에서 중복을 제하는 부분과 동일합니다.)
                    if((v&(1<<i))==0&&(H[i]!=H[i-1]||(v&(1<<(i-1)))!=0)){
                        //만들어진 숫자가 C보다 무조건 작지 않은 상태에서,
                        //C[ind]와 이번에 뽑을H[i]가 같다면 여전히 만들어질 숫자는 C보다 무조건 작지 않습니다.
                        if(!l&&C[ind]==H[i]){next=false;}
                        //만들어진 숫자가 C보다 무조건 작지 않은 상태에서,
                        //C[ind]가 이번에 뽑을 H[i]보다 커져 버린다면 만들어질 숫자는 C보다 무조건 작아집니다.
                        else if(!l&&C[ind]>H[i]){next=true;}
                        //이외의 경우는 만들어질 숫자가 C보다 무조건 작습니다.
                        else{next=true;}
                        //메모이제이션 합니다.
                        ret=(ret+mkCACHE(i,v|(1<<i),ind+1,(10*r+H[i]-'0')%M,next))%REM;
                    }
                }
            }
        }
        return ret;
    }
    
    int main(void){
        int i,d,T;
        long long temp;
        cin>>T;
        while(T--){
            C.clear();H.clear();d=0;temp=0;
            for(i=0;i<16;i++){
                memset(CACHE[i],-1,sizeof(int)*TWO16);
            }
            cin>>C>>M;
            //mkCACHE가 문제의 조건을 만족하면서 C보다 작거나 같은 수의 갯수를 반환하므로,
            //C와 같으면서 문제의 조건을 만족하는 경우는 아래와 같이 직접 예외처리 해줍니다.
            //(C가 m으로 나누어 떨어진다면 -1,아니라면 -0을 하게 됩니다.)
            for(i=0;i<C.size();i++){
                temp*=10;temp+=(C[i]-'0');
            }
            d=(temp%M==0)? 1:0;
            //구현의 용이성을 위해 H의 맨앞에는 '-'가 포함됩니다.
            H="-"+C;
            //H를 정렬합니다.
            sort(H.begin(),H.end());
            cout<<"ANSWER "<<mkCACHE(0,1,0,0,false)-d<<endl;
        }
        return 0;
    }
    

    JM book에서는 방문정보, 나머지 정보, less정보를 포함하여
    캐시설정을 3차원으로 하는 것을 보았습니다.
    제가 푼 풀이에서는 TSP2와 거의 동일한 캐시 설정을 사용합니다.
    책에서 메모이제이션을 하는
    이유는 재귀호출시 중복호출의 비효율성을 해결하기 위해서라고 읽었습니다.
    제 짧은 생각에는 less정보나,
    나머지 정보에 대해서는 중복으로 계산되는 부분이 없는것 같은데,
    혹시 중복으로 계산되는 부분이 있어서 3차원 캐시를 설정하신 건가요???

    제 코드의 문제점과 3차원 캐시를 사용하신 이유를 알고 싶습니다.


    10년 전
1개의 댓글이 있습니다.
  • canuyes
    canuyes

    3주일 동안 고민하다가 오늘에서야 이해했네요 ㅠㅠ.
    따져보면 왜 굳이 3차원 캐시를 사용하냐는데서 나온 고민이었습니다.
    여러방법으로 고민해보니까 정말 3차원 캐시가 필요하더군요...

    제가 벌려놓은 건 제가 수습해야 할것 같기도 하고,
    혹시나 행여나 만에하나 저같이 이해 못하는 분들이 계실수도 있으니,
    제가 문제 풀고 정리한 기록을 올립니다.

    개인적인 기록이다보니 말투가 디시체인점 양해 부탁드립니다. ㅠㅠ
    중복을 제하는 부분이나 나머지 다루는 부분등은 책과 동일합니다.
    왜 굳이 3차원 캐시를 사용해야만 하는지에 대한 기록입니다.

    기록

    기저사례를 제외한,
    나머지 처리나, 중복을 재하는 부분등은 JM book과 동일합니다.


    10년 전 link
  • 정회원 권한이 있어야 커멘트를 다실 수 있습니다. 정회원이 되시려면 온라인 저지에서 5문제 이상을 푸시고, 가입 후 7일 이상이 지나셔야 합니다. 현재 문제를 푸셨습니다.