题意
先上一道模板题:SP8073 The area of the union of circles
通过点和半径的方式给出平面上的一些圆,求这些圆覆盖的总面积。
题解
和这道题类似的还有P1222 三角形 。不过这道题由于一些特殊的性质,直接扫描线就可以过,并且效率远远高于自适应辛普森。
还是来看这道题。我们可以把平面上的一堆相交的圆看成一个整体函数,我们要求的就是每一段函数的面积和。很明显可以化成积分的形式,于是用自适应辛普森积分乱搞。
那么我们怎么求一个 x x x所对应的 f ( x ) f(x) f(x)值呢?很明显我们在 x x x确定的时候要求的就是直线 y = x y=x y=x被圆覆盖的总长度。那么我们可以枚举所有的圆,只要与直线 y = x y=x y=x有交点的圆,把两个交点之间的部分看成一条线段,然后贪心求线段覆盖长度即可。
一些小细节:自适应辛普森算法中多加几个参数,可以减少重复的计算。同时可以在开始求面积之前把被一个大圆覆盖的小圆先删除。然后对于每一堆通过交点联通的圆,求出最左端和最右端,然后进行自适应辛普森积分即可。
代码
#include <bits/stdc++.h>
#define MAX 2005
#define ll long long
#define eps 1e-7
#define INF 1e16
using namespace std;
struct seg{
double l, r;
friend bool operator <(seg a, seg b){
if(a.l == b.l) return a.r < b.r;
return a.l < b.l;
}
};
struct vec{
ll x, y;
};
double dis(vec a, vec b){
return sqrt(1.0*(a.x-b.x)*(a.x-b.x) + 1.0*(a.y-b.y)*(a.y-b.y));
}
struct cir{
vec o;
ll r;
friend bool operator <(cir a, cir b){
return a.r < b.r;
}
}a[MAX];
int n;
vector<seg> v;
int st, ed;
double f(double x){
//求f(x)
v.clear();
for (int i = st; i <= ed; ++i) {
//处理出交点的纵坐标,保存为线段
if(x > a[i].o.x+a[i].r || x < a[i].o.x-a[i].r) continue;
double l = x-a[i].o.x;
l = sqrt(a[i].r*a[i].r - l*l);
v.push_back((seg){
a[i].o.y-l, a[i].o.y+l});
}
if(v.empty()) return 0.0;
sort(v.begin(), v.end()); //以下贪心线段覆盖
double ans = 0, last = v[0].l;
for (int i = 0; i < v.size(); ++i) {
if(v[i].r > last){
ans += v[i].r-max(last, v[i].l);
last = v[i].r;
}
}
return ans;
}
double calc(double l, double r, double sl, double sr){
//直接计算一段区间的面积
double mid = (l+r)/2;
return (r-l)*(sl + 4.0*f(mid) + sr)/6.0;
}
double simpson(double l, double r, double S, double sl, double sr){
double mid = (l+r)/2, s = f(mid);
double t1 = calc(l, mid, sl, s), t2 = calc(mid, r, s, sr);
if(fabs(t1+t2-S) < eps) return S;
return simpson(l, mid, t1, sl, s)+simpson(mid, r, t2, s, sr);
}
void init(){
//预处理,删除被包含的圆
int cnt = 0;
for (int i = 1, j; i <= n; ++i) {
for (j = i+1; j <= n; ++j) {
if(a[i].r-a[j].r + dis(a[i].o, a[j].o) < eps){
break;
}
}
if(j > n) a[++cnt] = a[i];
}
n = cnt;
}
bool cmp(cir a, cir b){
//按圆的最左端排序,用于判断连续相交
return a.o.x-a.r < b.o.x-b.r;
}
double ans;
void solve(){
//寻找每段联通的圆,进行计算
double l, r;
int i = 1, j;
while(i <= n) {
l = a[i].o.x-a[i].r, r = a[i].o.x+a[i].r;
for (j = i+1; j <= n; ++j) {
if(a[j].o.x-a[j].r > r) break;
r = max(r, (double)a[j].o.x+a[j].r);
}
st = i, ed = j-1, i = j;
double mid = (l+r)/2;
ans += simpson(l, r, f(mid), f(l), f(r));
}
}
int main()
{
cin >> n;
double l = INF, r = -INF;
for (int i = 1; i <= n; ++i) {
scanf("%lld%lld%lld", &a[i].o.x, &a[i].o.y, &a[i].r);
l = min(l, (double)a[i].o.x-a[i].r);
r = max(r, (double)a[i].o.x+a[i].r);
}
sort(a+1, a+n+1);
init();
sort(a+1, a+n+1, cmp);
solve();
printf("%.3lf\n", ans);
return 0;
}