ANDROMEDA 오답 : 도움 부탁드립니다.

  • web3p
    web3p

    잘 안되네요. 뭔가 잘못 생각하고 있는거 같은데 고수님들의 도움 부탁드립니다.

    먼저, 게임을 사각테이블(nxn)로 모형화했습니다.

    1. 0행 0열에서 1값을 가지고 준비
    2. 1회 시행에서 1행0열로 3으로 나눈 값이 복제 (이긴 경우) 0행1열로 3으로 나눈 값이 복제 ( 진 경우) 0행0열에 3으로 나눈 값이 남음 (비긴 경우)
    3. 2회 시행에서 [1,0]/3 -> [2,0]로, [0,1]/3 -> [0,2]로, ([1,0]+[0,1])/3 -> [1,1]로 각각 복제
    4. 원래 각 셀이 가지고 있던 값의 1/3이 남고, 1/3이 아래로 더해지고, 1/3이 우로 더해지면서 게임이 진행
    5. n행에 도달하여 사각테이블을 벗어나는 값은 게임에 이긴 경우로 승리확률(w)에 누적되고 n열에 도달하여 사각테이블을 벗어나는 값은 게임에 진 경우
    6. 게임에 이긴 경우와 진 경우는 0행0열에 더해져 게임 진행

    그리고, 게임을 계속할지 포기할지를 판단하기 위해 미리 다음과 같은 확률을 준비했습니다.

    1. u[0]: 게임을 포기하고 재시작하는 확률. 정상적으로 게임을 진행한다는 가정으로 확률 계산 0행0열에서 1값을 가지고 게임 시작
    2. u[k]: 게임을 포기없이 계속하는 확률 k (= r * n + c) r행c열에서 게임을 계속하는 확률 r행c열에서 1값을 가지고 게임 시작

    본 게임은 각각의 시행 후에 진 경우가 많을 때 즉 r < c인 셀에 한하여 u[0]와 u[k]를 비교하여 u[0]가 크면 포기하는 경우로 해당 셀을 재시작하기 위해 b에 누적하고 0로 만듭니다

    이와같이 게임을 진행하면 초기에 변동이 심하고 중간에 증가량이 오차범위 내에서 일정하게 증가하다가 말기에 다시 변동성이 증가합니다.

    그래서 일정하게 증가하는 중간부분을 건너뛰기 위해 변칙처리 했습니다.
    1000회를 넘는 경우 749회까지 시행후 750회의 증가량으로 중간부분을 계산하였고 나머지 250회의 시행으로 말기의 변동성이 큰부분을 커버했습니다.

    #include <stdio.h>
    #include <memory.h>
    #include <vector>
    
    using namespace std;
    
    class Rule
    {                           vector<double> p;
    public:
        Rule (int r, int c);
        ~Rule() { p.clear(); }
        double win (int t);
    };
    
    // w:승리확률 저장, b:재게임 저장
    int s, n;                   Rule *u[100];       double w, b, a[100];
    
    inline double move ()       // 게임을 사각테이블(nxn)로 모형화
    {
            // 이전 종료게임을 재시작시키고 초기화
            int k, x = n * n - 1;           a[0] += b;      b = 0.0;
    
            // n회 먼저 승리한 게임. 재시작을 위해 b에 저장
            // n회 먼저 패배한 게임. 재시작을 위해 b에 저장
            for (k = n; k --;) b += a[x-k] / 3.0;           w += b;
            for (k = x; k > 0; k -= n) b += a[k] / 3.0;
    
            // 1회 시행후 이기면 아래로, 지면 우로 이동, 비기면 제자리
            while (x >= n) {    k = n - 1;
                while(k --) {
                    a[x] = (a[x] + a[x-n] + a[x-1]) / 3.0;  x --;
                }   a[x] = (a[x] + a[x-n]) / 3.0;           x --;
            }   while(x) {
                    a[x] = (a[x] + a[x-1]) / 3.0;           x --;
                }
            a[0] /= 3.0;        return w;
    }
    
    Rule::Rule (int r, int c)
    {
        // 사각테이블 초기화 및 시작위치 설정
        memset(a, 0, 8*n*n);    a[r*n+c] = 1.0;     w = b = 0.0;
    
        // 1000회의 시행이면 증가폭이 오차범위 내에서 움직인다고 봄.
        // 이후 일정하게 증가함.
        int e = (s < 1000) ? s : 1000;              p.assign(e, 0.0);
    
        // 최대 1000회 시행 승리확률을 벡터 p에 저장.
        for (int t = 1; t < e; t ++) p[t] = move();
    }
    
    double Rule::win (int t)
    {
        // 계속시행or재시행시 잔여시행의 승리확률 가져옴(비교용).
        int x = s - t;          return x < 1000 ? p[x] : p[999];
    }
    
    inline void eval (int& t, int e)
    {                                               int r, c, k;
        while (t <= e) {        move ();
    
            // 패배가 많은 경우 포기or계속 결정. u[0]:재시작, u[k]:계속시행
            for (r = 0; r < n-1; r ++)  for (c = n-1; c > r; c --) {
                if (u[0]->win(t) > u[k=r*n+c]->win(t)) {
                    b += a[k];      a[k] = 0.0;
                }
            }   t ++;
        }
    }
    
    double play ()
    {
        // u[0] : 재시작시 승리확률 미리 계산
        // u[k] : 계속시행 승리확률 미리 계산
        int t, r, c;                  u[0] = new Rule(0,0);
        for(r = 0; r < n-1; r ++)
            for(c = n-1; c > r; c --) u[r*n+c] = new Rule(r,c);
    
        memset(a, 0, 8*n*n);    a[0] = 1.0;     w = b = 0.0;    t = 1;
    
        // s가 충분히 크면 초기에 변동성이 크다가 중간에서 일정하게 증가후
        // 말기에 다시 변동성 증가함. 일정하게 증가하는 중간부분 건너뜀
        if (s > 1000) {
            eval (t, 749);      double d = w;   eval (t, 750);  d = w - d;
            w += (s-1000) * d;                  t = s - 249;
        }   eval (t, s);
    
        for(r = 0; r < n-1; r ++)
            for(c = n-1; c > r; c --) delete u[r*n+c];
        delete u[0];            return w;
    }
    
    int main ()
    {
        int t;                  scanf ("%d", &t);
        while (t --) {          scanf ("%d %d", &s, &n);
            printf ("%.8f\n", (n > 1) ? play() : ((double) s / 3.0));
        }   return 0;
    }
    

    10년 전
7개의 댓글이 있습니다.
  • JongMan
    JongMan

    소스코드를 알아보기 힘든데 알아보기 쉽게 포매팅을 좀 해주시고, 어떤 방법을 쓰는지 설명해 주셔야 답변을 받으실 수 있을 것 같네요.


    10년 전 link
  • web3p
    web3p

    다시 정리했습니다.


    10년 전 link
  • VOCList
    VOCList

    WA이신가요? 변칙 처리를 통하여 얻은 값이 문제에서 요구하는 정해와의 오차 1e-7 이내에 늘 들어가리란 보장을 하실 수 있을 때에 해당 변칙 처리가 본 문제에서 의미있는 솔루션이 될 것 같습니다.


    10년 전 link
  • web3p
    web3p

    충분히 작다는 걸 확인하고 750에서 자른겁니다.


    10년 전 link
  • Being
    Being

    음.. 어떤 풀이인지 제가 잘 이해가 되지 않아 도움을 드리기 어렵습니다. 일단 대강은 맞는 것 같은데 답의 정밀도가 문제가 되는 것 같네요. 큰 데이터에 대해 올려 주신 코드를 돌린 결과와 상대 오차를 계산하면 대략 0.1% 수준 정도네요. 상당히 큰 정밀도 향상이 필요할 것 같습니다.


    10년 전 link
  • web3p
    web3p

    도움 감사합니다. 상대오차 0.1%라면 14285714.12246847에서 만단위 숫자 8이 달라지는 건가요?


    10년 전 link
  • JongMan
    JongMan

    네 맞을겁니다. 채점 서버는 1e-8, 다시 말해 0.000001% 이하의 상대 오차를 요구합니다.


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