[BZOJ1502][NOI2005]月下柠檬树

首先,数学书上说,平行光产生的投影形状和大小都不会改变= =。因此,这一个个的圆台的投影就是一个个圆和它们之间的公切线。让x轴穿过所有圆的圆心,这样就变成了求x轴上方面积*2。

这题可以用自适应Simpson来做(似乎只要不是FJTSC,求面积并都可以用自适应simpson??)。自适应simpson的主要思想是:根据[Simpson’s rule](http://en.wikipedia.org/wiki/Simpson’s_rule)计算一个函数f(x)[lef, rig]上的近似面积,然后分别计算在[lef, (lef+rig)/2], [(lef+rig)/2, rig]上的近似面积,如果两者之和约等于前者(abs(S1 + S1 - S) < EPS),那么就返回近似面积,否则递归计算左右两边。

自适应simpson看起来十分轻松愉悦,但存在下面2个风险:

  • 不保证正确性:可以构造锯齿函数让递归的第一次计算就符合要求,但实际上仅仅是第一次误差很小,如果细分的话就会发现面积误差很大。
  • 不保证复杂度:即使没有卡simpson的数据,EPS太小会导致TLE,EPS太大会导致精度误差WA。并且,一般情况下,计算f(x)的复杂度有O(n)

另一方面,自适应simpson的编程复杂度还是非常愉悦的,可以作为考场的骗分利器!

在实现的时候,建议还是传几个参数进去,这样可以减少计算函数值的次数,速度快很多!

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
55
56
57
58
59
#include <cmath>
#include <cstdio>
#include <algorithm>

const double EPS = 1e-6;
inline int sign(const double x) { return x < -EPS ? -1 : x > EPS; }
inline double sqr(const double x) { return x*x; }
int n;
double alpha, lef = 1e10, rig = -1e10, h[501], r[501], X1[501], Y1[501], X2[501], Y2[501];
inline double f(const double x)
{
  double fun = .0, tmp;
  for (int i = 0; i < n; ++i)
    if (sign(tmp = sqr(r[i]) - sqr(x-h[i])) >= 0)
      fun = std::max(fun, tmp);
  fun = std::sqrt(fun);
  for (int i = 0; i < n; ++i)
    if (0 <= sign(x-X1[i]) && sign(x-X2[i]) <= 0)
      fun = std::max(fun, Y1[i] + (Y2[i]-Y1[i])*(x-X1[i])/(X2[i]-X1[i]));
  return fun;
}
inline double simpson(const double l, const double r, const double fl, const double fm, const double fr)
  { return (r-l) * (fl + 4.0*fm + fr) / 6.0; }
inline double autosimpson(const double l, const double r, const double fl, const double fm, const double fr)
{
  static double sl, sr, whole;
  const double m = (l+r)/2.0;
  const double tmp1 = f((l+m)/2), tmp2 = f((m+r)/2);
  sl = simpson(l, m, fl, tmp1, fm), sr = simpson(m, r, fm, tmp2, fr), whole = simpson(l, r, fl, fm, fr);
  if (sign(sl+sr-whole) == 0) return whole;
  return autosimpson(l, m, fl, tmp1, fm) + autosimpson(m, r, fm, tmp2, fr);
}
int main()
{
  scanf("%d%lf", &n, &alpha);
  alpha = 1 / std::tan(alpha);
  for (int i = 0; i <= n; ++i)
  {
    scanf("%lf", h+i);
    h[i] *= alpha;
    if (i) h[i] += h[i-1];
  }
  for (int i = 0; i < n; ++i)
  {
    scanf("%lf", r+i);
    lef = std::min(lef, h[i]-r[i]);
    rig = std::max(rig, h[i]+r[i]);
  }
  rig = std::max(rig, h[n]);
  for (int i = 0; i < n; ++i)
    if (sign(h[i+1]-h[i]-std::fabs(r[i+1]-r[i])) > 0)
    {
      X1[i] = h[i] + r[i] * (r[i]-r[i+1]) / (h[i+1]-h[i]);
      Y1[i] = std::sqrt(sqr(r[i])-sqr(X1[i]-h[i]));
      X2[i] = h[i+1] + r[i+1] * (r[i]-r[i+1]) / (h[i+1]-h[i]);
      Y2[i] = std::sqrt(sqr(r[i+1])-sqr(X2[i]-h[i+1]));
    }
  printf("%.2f", autosimpson(lef, rig, f(lef), f((lef+rig)/2), f(rig)) * 2.0);
}

Comments