본문 바로가기

Study/Algorithm

[동적계획법] 10.달팽이

우물을 기어오르는 달팽이(예제)를 응용한 '장마가 찾아왔다'

https://algospot.com/judge/problem/read/SNAIL

 

algospot.com :: SNAIL

달팽이 문제 정보 문제 깊이가 n 미터인 우물의 맨 밑바닥에 달팽이가 있습니다. 이 달팽이는 우물의 맨 위까지 기어올라가고 싶어하는데, 달팽이의 움직임은 그 날의 날씨에 좌우됩니다. 만약 비가 내리면 달팽이는 하루에 2미터를 기어올라갈 수 있지만, 날이 맑으면 1미터밖에 올라가지 못합니다. 여름 장마가 찾아와, 앞으로 m 일간 각 날짜에 비가 올 확률이 정확히 75%일 전망입니다. m 일 안에 달팽이가 우물 끝까지 올라갈 확률을 계산하는 프로그램을 작성하

algospot.com

책과 홈페이지에 나온 문제가 약간 다르다!! 책은 비오면 1m올라가는데 홈페이지엔 2m올라간다고 되어있음

이것때문에 계속 틀리고 있었다. ㅂㄷㅂㄷ..

 

 

컨셉

 

달팽이가 올라가서 도착할 수 있는 확률을 점화식으로 나타냈다.

climb(days, climbed) // 달팽이가 days만큼 climb미터를 올라갈 수 있는 확률

 

 

 

초안

내가 쓴 답(오답, 56ms) ; 오차범위 조심

...더보기

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
 * @name    8.12.장마가 찾아왔다.
 * @ID      SNAIL
 * @date    2019-08-12
 * @author  Sunmon
 */
 
#include <iostream>
#include <cstring>
#include <algorithm>    // std::fill()
 
using namespace std;
double cache[1000+1][1000+1];   //climb[days][climbed]저장
int DAYS, DEPTH;   //목표 DEPTH미터 DAYS일
 
//days날에 climbed미터만큼 남았을 때, m일에 n미터 이상 올라갈 확률
double climb(int days, int climbed)
{
    if(days <= 0 ) return climbed <= 0;
    double& ret = cache[days][climbed];
    if(ret != -1return ret;
 
    //비가 오면 2미터, 안 오면 1미터 올라감.
    //비 올 확률 75%, 안 올 확률 25%
    return ret = climb(days-1, climbed-1/4 + climb(days-1, climbed-2* 3 / 4;
}
 
 
int main()
{
    int C;
    cin >> C;
 
    while(C--)
    {
        cin >> DEPTH >> DAYS ;
        // memset(cache, sizeof(cache), -1);   //qNAN Error !!
        fill_n(cache[0], 1001 * 1001-1);
        cout << climb(DAYS, DEPTH) << endl;
    }
    return 0;
}
 
 
 
 
cs

 

 

재안

내가 쓴 답(정답, 60ms)

...더보기

소수점 아래 10자리까지 표현하게 했더니 맞았다. ㅂㄷㅂㄷ

오차 범위 조심하자. 소수점 몇째자리까지 나타내느냐에 따라 달라진다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
 * @name    8.12.장마가 찾아왔다.
 * @ID      SNAIL
 * @date    2019-08-12
 * @author  Sunmon
 */
 
#include <iostream>
#include <cstring>
#include <algorithm>    // std::fill()
#include <iomanip>      // setprecision()
 
using namespace std;
double cache[1000+1][1000+1];   //climb[days][climbed]저장
int DAYS, DEPTH;   //목표 DEPTH미터 DAYS일
 
//days날에 climbed미터만큼 남았을 때, m일에 n미터 이상 올라갈 확률
double climb(int days, int climbed)
{
    if(days <= 0 ) return climbed <= 0;
    double& ret = cache[days][climbed];
    if(ret != -1return ret;
 
    //비가 오면 2미터, 안 오면 1미터 올라감.
    //비 올 확률 75%, 안 올 확률 25%
    return ret = climb(days-1, climbed-1/4 + climb(days-1, climbed-2* 3 / 4;
}
 
 
int main()
{
    int C;
    cin >> C;
 
    while(C--)
    {
        cin >> DEPTH >> DAYS ;
        // memset(cache, sizeof(cache), -1);   //qNAN Error !!
        fill_n(cache[0], 1001 * 1001-1);
 
        //소수점 10자리까지 표현
        // cout << fixed;
        // cout.precision(10);
        // cout <<  climb(DAYS, DEPTH) << endl;
 
        //소수점 10자리까지 표현 2
        cout << fixed << setprecision(10<<  climb(DAYS, DEPTH) << endl;
    }
    return 0;
}
 
 
 
 
cs

 

 

 

주의

 

* -1.#QNAN 에러::

double형 배열memset을 이용하여 초기화 했을 때 발생.

 

1
2
double cache[1000][1000];
memset(cache, -1sizeof(cache));    //-1.#QNQN error 발생!!
cs

 

NAN은 알다시피 "Not A Number"의 줄임말이다. 숫자가 아닌데 숫자 연산을 하면 나타나는 에러다. 실수형(double, float)연산을 할 때 종종 나타난다. 부동소수점으로 표현 할 수 없는 경우에도 나온다 (ex: 0/0, √-1))

이 NAN은 quiet NaNs와 signaling NaNs의 두 가지 종류가 있는데, 지금 상황에선 Quiet Nans에러가 걸린 것.

qNaN(조용한 NaN)은 유효하지 않은 값이나 연산자로 계산을 했을 때 나오는 에러다. 반대로 sNaN은 몰라 파파고 돌려봐도 모르겠다. 직접 링크로 가서 읽어볼 것.

 

아무튼 이 에러가 나타난 원인은 이거다:: double형을 memset으로 초기화 한 것.

`memset(n)`은 입력한 주소의 처음 n바이트를 c로 초기화한다. (The memset() function fills the first n bytes of the memory area pointed to by s with the constant byte c.) 그런데 double형은 실수형이라서 숫자 표현 방식이 정수와 다르기 때문에 memset을 쓰면 이상하게 표현이 될 수 밖에 없다.

애초에 정수형을 쓸 때도 왜 memset(0)혹은 memset(-1)로만 가능했는 지 생각해보자.

정수형 : (int 4바이트를 0으로 채운다) 0000 → (값) 0 / (int 4바이트를 -1로 채운다)1111 → (값) -1

이렇게 표현이 되었기 때문에 memset을 쓰던 건데, 실수형은 값을 저장하는 방식이 달라서 1111을 채운다고 해서 -1이 저장되지 않는다.

 

해결법:

 

1. array가 아닌 vector를 이용해서 값을 초기화한다.

2. for문을 돌린다.

3. stl::fill() or fill_n()을 사용한다   //fill(v.begin(), v.end(), const), #include <algorithm>

 

세번째 방법을 사용하는게 지금 memset쓰는 거랑 비슷하다.

주의할건 fill함수 안에 sizeof를 넣으면 안 된다는 거다. 왜인지는 모르겠는데, 아무튼 오류가 난다.

 

fill(): fill(v.begin(), v.end(), 숫자)인데, 2차 배열에서는 end를 구해주기 귀찮더라. 그냥 fill_n쓰는게 편하다.

fill_n(v.begin(), 연속해서 채울 범위, 채울 숫자) 이렇게 쓴다. 단, 채울 범위에는 맨 처음 begin도 포함이다.

 

이를 이용해서, 아까 memset으로 오류나던 걸 다음과 같이 바꿔줬다.

1
2
double cache[1000+1][1000+1];
fill_n(cache[0], 1001* 1001-1);
cs

 

 

* 소수점 나타내기 ::
1
2
3
4
5
6
7
8
9
//소수점 10자리까지 표현
cout << fixed;
cout.precision(10);
cout <<  climb(DAYS, DEPTH) << endl;
 
 
//소수점 10자리까지 표현 2
#include <iomanip>        // setprecision()을 포함하는 헤더
cout << fixed << setprecision(10<<  climb(DAYS, DEPTH) << endl;
cs

 

 

 

 

 

참고

 

fill_n(): http://www.cplusplus.com/reference/algorithm/fill_n/

 

fill_n - C++ Reference

123456789 template OutputIterator fill_n (OutputIterator first, Size n, const T& val) { while (n>0) { *first = val; ++first; --n; } return first; // since C++11 }

www.cplusplus.com

 

NaN 참고

- Nan: https://en.m.wikipedia.org/wiki/NaN

 

NaN - Wikipedia

In computing, NaN, standing for not a number, is a numeric data type value representing an undefined or unrepresentable value, especially in floating-point arithmetic. Systematic use of NaNs was introduced by the IEEE 754 floating-point standard in 1985, a

en.m.wikipedia.org

- qNan: http://mathworld.wolfram.com/QuietNaN.html

- sNaN: http://mathworld.wolfram.com/SignalingNaN.html

 

 

Signaling NaN -- from Wolfram MathWorld

Signaling NaN In the IEEE 754-2008 standard (referred to as IEEE 754 henceforth), a signaling NaN or sNaN is a NaN which is signaling in the sense of being most commonly returned in conjunction with various exceptions and handling mechanisms defined theref

mathworld.wolfram.com

 

http://mathworld.wolfram.com/QuietNaN.html

 

mathworld.wolfram.com

 

 

 

https://stackoverflow.com/questions/27581320/using-memset-in-double-type-array

 

using memset() in double type array

I want to set all the index value to -1 in a double array. Here is my code : double dp[505]; memset(dp,-1,sizeof(dp)); cout<<dp[0]<<"\n"; but="" it="" is="" showing="" nan="" when="" i="" try="" to="" print="" its...<="" p=""> </dp[0]<<"\n";>

stackoverflow.com