https://www.acmicpc.net/problem/9206
9206번: 나무 말고 꽃
선영이와 남자친구의 2주년이 얼마 남지 않았다. 선영이는 그를 위해 특별한 것을 사주려고 한다. 남자친구는 나무에 관심이 매우 많다. 하지만, 선영이는 나무는 선물로 매우 크다고 생각한다.
www.acmicpc.net
수치해석 문제들이 다 그렇듯이 수많은 시도를 해야 풀 수 있다... 일단 constexpr로 각종 상수를 정의해 주자. constexpr value들은 컴파일 시점에 평가되기 때문에, #define 같은 매크로의 기능을 거의 모두 쓸 수 있으면서 그것보다 더 안전하다고 할 수 있겠다.
constexpr long double PI = 3.1415926535897932384626l;
constexpr int STEP = 20000;
PI의 맨 끝에 l을 안 붙이면 long double이 아니라 double로 인식이 돼서 WA를 받는다. (난 그것 때문에 개고생했다) 정확히는, PI 자체는 long double형 변수가 되는데 PI를 init하는 값은 double이라 정확도를 자기 스스로 깎아먹는 짓을 하는 것이다.
주어진 식은 부정적분을 초등함수 내에서 나타낼 수 없으므로 수치해석 기법을 써야 한다. 떠오르는 접근 방법은 테일러 급수와 심슨 공식의 2가지가 있을 수 있는데 난 첫 번째로는 WA를 받고 두 번째로 AC를 받았다. 그 이유를 살펴보자.
테일러 급수
주어진 수식
는 곱셈공식에 의해 기초 함수들의 덧셈으로 쪼개질 수 있다. 이것들을 일일이 테일러 전개해 부정적분을 무한급수 꼴로 나타낸 뒤, h를 대입하면 끝. 그러나 다음과 같이 주어지는 테일러 급수의 오차항을 생각해 보자.
(n은 n차항까지 사용했음을 의미한다.) 이 식은 오차의 대략적인 모습을 제공하는데, 주요하게 보아야 할 항은
심슨 공식
심슨 공식은 라그랑주 보간법을 이용해 임의의 곡선을 2차함수 또는 3차함수의 연속으로 근사하면서 적분한 결과물이다. 유도 과정은 생략하고, 우리는 3차함수의 근사를 사용하도록 하자.
물론 이런 하찮은 근사로 10^-4 수준 정확도가 나올 리가 없다. 그래서 마치 구분구적법 하는 것처럼 구간을 잘게 쪼개서 각 구간마다 심슨 공식을 따로 사용할 것이다.
long double simpson_a_part(const Bottle &b, long double s, long double e) {
long double m1 = (2 * s + e) / 3, m2 = (s + 2 * e) / 3;
return (e - s) / 8 * (b(s) + 3 * b(m1) + 3 * b(m2) + b(e));
}
long double simpson_integration(const Bottle &b, long double s, long double e) {
long double step = (e - s) / STEP, ret = 0;
for (int i = 0; i < STEP; ++i) {
ret += simpson_a_part(b, s + i * step, s + (i + 1) * step);
}
return ret;
}
클래스 Bottle은 함수 객체다. 첫 번째 함수에서 볼 수 있듯이 Bottle의 인스턴스를 마치 함수처럼 취급할 수 있다. Bottle 클래스도 이해를 돕기 위해 삽입해야겠다.
class Bottle {
public:
long double a, b;
long double operator()(long double x) const {
long double R = a * std::exp(-x * x) + b * std::sqrt(x);
return R * R * PI;
}
};
아니면 람다 함수를 사용해서 함수 객체를 만드는 것도 가능하다.
using Bottle = std::function<long double(long double)>;
Bottle bottle_factory(long double a, long double b) {
return [a, b](long double x) -> long double {
long double R = a * std::exp(-x * x) + b * std::sqrt(x);
return R * R * PI;
};
}
여기서 std::exp 대신 그냥 <cmath>에 정의된 exp를 쓰면 long double이 강제로 double로 바뀌기 때문에 오차가 난다. sqrt도 마찬가지.
오차항의 상한은 다음과 같다.
테일러 공식 때와 달리, (b - a) 값은 우리가 조절 가능한 값이다. 그래서 오차항을 관리하는 데 그렇게 큰 신경을 쓸 필요가 없다.

'Computer Science > PS' 카테고리의 다른 글
[백준 25384] 니은숲 예술가 (Diamond V) (0) | 2022.08.31 |
---|---|
[백준 5615] 아파트 임대 (Feat. 밀러-라빈 소수 판정법) (Platinum I) (0) | 2022.08.28 |
[백준 18222] 투에-모스 문자열 (Silver II) 5줄 안으로 구현하기 (0) | 2022.08.20 |
[백준 25323] 수 정렬하기, 근데 이제 제곱수를 곁들인 [UCPC 2022 예선] (Platinum V) (0) | 2022.08.20 |
[백준 1168] 요세푸스 문제 2 (Platinum III) (Feat. PBDS) - 세그먼트 트리 없이 풀기 (0) | 2022.08.17 |
댓글