【模板】圆的面积并(自适应辛普森乱搞)

题意

先上一道模板题: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;
}

猜你喜欢

转载自blog.csdn.net/qq_30115697/article/details/90930007