P4207 [NOI2005]月下柠檬树
如图,我们要求的面积就是这些圆形跟梯形的组合,由于投射到地面上,显然有h′=htanθh' = \frac{h}{tan \theta}h′=tanθh,由此我们就可以开始推导这个f(x)f(x)f(x)函数了。
所以转换为我们要推导出直线a,ba, ba,b的函数表达式了。
有公切线ababab,矩形abgcabgcabgc,所以显然有eg=rc−reeg = r_c - r_eeg=rc−re,由三角形acd∼ceg∼befacd \sim ceg \sim befacd∼ceg∼bef,由此我们可以得到cd,efcd, efcd,ef从而得到ad,bfad, bfad,bf,知道这里对圆公切线问题就已经解决了。
所以接下来我们知道带到函数中利用自适应辛普森求解即可,详细细节见代码描述。
/*Author : lifehappy
*/
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>using namespace std;typedef long long ll;const int inf = 0x3f3f3f3f;
const double eps = 1e-6;const int N = 510;int n;double angle;struct Point {double x, y;
}circle[N], line[N << 1];void get_point() {//求得切线的左右两个坐标。for(int i = 1; i < n; i++) {double x1 = circle[i].x, x2 = circle[i + 1].x, r1 = circle[i].y, r2 = circle[i + 1].y;double ce = r1 * (r1 - r2) / (x2 - x1), xa = x1 + ce, ya = sqrt(r1 * r1 - ce * ce);double gd = r2 * (r1 - r2) / (x2 - x1), xb = x2 + gd, yb = sqrt(r2 * r2 - gd * gd);line[2 * i - 1] = {xa, ya}, line[2 * i] = {xb, yb};}
}double f(double x) {double ans = 0.0;for(int i = 1; i <= n; i++) {//在圆里面,if(x < circle[i].x + circle[i].y && x > circle[i].x - circle[i].y) {ans = max(ans, sqrt(circle[i].y * circle[i].y - (x - circle[i].x) * (x - circle[i].x)));}}for(int i = 1; i <= 2 * (n - 1); i += 2) {//被切线包围,if (x >= line[i].x && x <= line[i + 1].x) {ans = max(ans, (line[i + 1].y - line[i].y) / (line[i + 1].x - line[i].x) * (x - line[i].x) + line[i].y);}}//这些值一定是取最大嘛,因为它可能同时符合圆,切线的要求,所以我们得取覆盖面积最大的。return ans;
}double sim(double l, double r) {return (r - l) * (f(l) + f(r) + f((l + r) / 2.0) * 4.0) / 6.0;
}double asr(double l, double r, double eps, double ans) {double mid = (l + r) / 2.0;double ansl = sim(l, mid), ansr = sim(mid, r);if(fabs(ansl + ansr - ans) < 15.0 * eps) return ansl + ansr + (ansl + ansr - ans) / 15.0;return asr(l, mid, eps / 2.0, ansl) + asr(mid, r, eps / 2.0, ansr);
}int main() {// freopen("in.txt", "r", stdin);// freopen("out.txt", "w", stdout);// ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);scanf("%d %lf", &n, &angle);angle = tan(angle);n++;for(int i = 1; i <= n; i++) { scanf("%lf", &circle[i].x);circle[i].x /= angle;//改变x的值,求个前缀和得到对应的准确的坐标。circle[i].x += circle[i - 1].x;}for(int i = 1; i < n; i++) {scanf("%lf", &circle[i].y);}circle[n].y = 0;get_point();double l = circle[1].x - circle[1].y, r = circle[n].x;//确定积分区间。for(int i = 1; i <= n; i++) {l = min(l, circle[i].x - circle[i].y);r = max(r, circle[i].x + circle[i].y);}printf("%.2f", 2.0 * asr(l, r, eps, sim(l, r)));return 0;
}