これかーーー数学ゲーじゃないかと物議を醸した問題。でも普通にいい問題だと思う!!
問題概要
底面が一辺 cm の正方形であり、高さが cm であるような直方体型の水筒に、体積 cm3 の水を入れる。
底面の正方形の一辺を軸として、この水筒を徐々に傾ける。水を溢れさせずに水筒を傾けることができる最大の角度を求めよ。
考えたこと
確かに で解ける数学ゲーだけど、問題のシチュエーション自体はゲームプログラミングで部分的に出てきそうな雰囲気だし、教育的な問題だと思う。それに、 で殴れなくても、二分探索する解法もある。
ところで、この問題は三次元の問題だけど、本質的には二次元の問題といえる。 はあらかじめ で割って、立方体の側面に関する二次元幾何の問題として解くことにする。つまり、あらかじめ
としておく。 さて、水がこぼれるシチュエーションは大きく分けて二通りある。
このうちのどちらのシチュエーションになるのかについては簡単に求めることができる。
- 前者: のとき
- 後者: のとき
幾何をする
幾何をしてみると、下図のようになる。前者については
- 白い直角三角形のところの面積は となる
- よって、白い直角三角形の、長さ の辺以外のもう一つの辺の長さは となる
という風に求められる。後者についても同様に
- 水色の直角三角形の面積は である
- よって、水色の直角三角形の、長さ の辺以外のもう一つの辺の長さは となる
という風に求められる。
この図から言えることは、
- 前者については、
- 後者については、
ということがわかる。よって求める角度 は、
- 前者:
- 後者:
となる。
#include <bits/stdc++.h> using namespace std; int main() { const double PI = acos(-1.0); double a, b, x; cin >> a >> b >> x; x /= a; cout << fixed << setprecision(10); if (x > a*b/2) cout << atan2((a*b-x)*2, a*a) * 180 / PI << endl; else cout << atan2(b*b, x*2) * 180 / PI << endl; }
別解: 二分探索
角度を としたときの、水の面積を とすると、この問題は
という方程式を解け
という問題だと言える。このような方程式の解を求める方法として、二分探索はとても有力だ。あとは素直に、関数 を実装すれば OK!!!
#include <bits/stdc++.h> using namespace std; int main() { const double PI = acos(-1.0); double a, b, x; cin >> a >> b >> x; x /= a; auto f = [&](double t) { // 前者 if (tan(t) <= b/a) return a*b - a*(a*tan(t))/2; // 後者 else return b*(b/tan(t))/2; }; double left = 0, right = PI/2; for (int iter = 0; iter < 100; ++iter) { double theta = (left + right) / 2; if (f(theta) >= x) left = theta; else right = theta; } cout << fixed << setprecision(10) << left*180/PI << endl; }