[BZOJ1043][HAOI2008]下落的圆盘

这题就是经典的圆周长并(实际上周长并指的是覆盖,不然的话,- -||)。算是比较水的题目。

首先,这题的n不是很大,我们可以得到一个非常朴素的O(n^2)的方法:对于每个圆,枚举它后面掉下来的所有的圆,然后计算没被覆盖的部分。我们可以发现,对于一个圆,如果以它为原点建立极坐标系,每个圆覆盖它的部分都是一段连续的弧度区间。如果把所有的区间求出来了,那么剩下的问题就是经典的线段覆盖问题了。

我们来看一下这个覆盖区间怎么求。

ccir1

如图,用绿色和蓝色标出来的两个角度我们都可以求出来:绿色的是atan2(A.y-O.y, B.x-O.x),蓝色的可以通过△AED上的余弦定理算出来。为什么绿色的不用余弦定理呢?因为余弦定理的角只能是[0, pi],根本无法分清A在O的上方还是下方(这个地方我被坑了)。

为了方便做线段覆盖,最好还是把角度区间统一一下,比如全部变成[0, 2pi]。这个时候就要注意,如果有跨过2pi的区间,要拆成两部分。

代码还是比较短的:

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include <cmath>
#include <cstdio>
#include <vector>
#include <algorithm>
using std::pair;
using std::vector;

inline long double sqr(const long double x) { return x*x; }
inline long double dist(const long double x1, const long double y1, const long double x2, const long double y2)
  { return std::sqrt(sqr(x1-x2) + sqr(y1-y2)); }
inline long double angle(const long double A, const long double B, const long double C)
  { return std::acos((sqr(A)+sqr(B)-sqr(C))/(2*A*B)); }
inline int sign(const long double x)
{
  static const long double EPS = 1e-8;
  if (x > EPS) return 1;
  return x < -EPS ? -1 : 0;
}
const int MAXN = 1000;
const long double PI = std::acos(-1.0);
int n;
double R[MAXN], A[MAXN], B[MAXN];
vector< pair<long double, long double> > seg, cover;
int main()
{
  scanf("%d", &n);
  for (int i = 0; i < n; ++i)
    scanf("%lf%lf%lf", R+i, A+i, B+i);
  long double ans = .0;
  for (int i = 0; i < n; ++i)
  {
    seg.clear();
    bool covered = false;
    for (int j = i+1; j < n; ++j)
    {
      const long double d = dist(A[i], B[i], A[j], B[j]);
      if (sign(R[j]-R[i]) >= 0 && sign(d-(R[j]-R[i])) <= 0)
      {
        covered = true;
        break;
      }
      if (sign(d-(R[j]+R[i])) >= 0 || sign(d-std::abs(R[j]-R[i])) <= 0)
        continue;
      const long double alpha = std::atan2(B[j]-B[i], A[j]-A[i]);
      const long double beta = angle(R[i], d, R[j]);
      const pair<long double, long double> tmp(alpha-beta, alpha+beta);
      if (sign(tmp.first) <= 0 && sign(tmp.second) <= 0)
        seg.push_back(pair<long double, long double>(2*PI+tmp.first, 2*PI+tmp.second));
      else if (sign(tmp.first) < 0)
      {
        seg.push_back(pair<long double, long double>(2*PI+tmp.first, 2*PI));
        seg.push_back(pair<long double, long double>(0, tmp.second));
      }
      else
        seg.push_back(tmp);
    }
    if (covered)
      continue;

    seg.push_back(pair<long double, long double>(10, 10));
    std::sort(seg.begin(), seg.end());
    long double ang = 0, lef = 0, rig = 0;
    for (vector< pair<long double, long double> >::iterator iter = seg.begin(); iter != seg.end(); ++iter)
    {
      if (sign(rig - iter->first) >= 0)
        rig = std::max(rig, iter->second);
      else
      {
        ang += rig-lef;
        lef = iter->first, rig = iter->second;
      }
    }
    ans += R[i]*(2*PI-ang);
  }
  printf("%.3f", static_cast<double>(ans));
}

Comments