zoukankan      html  css  js  c++  java
  • 多个圆面积的并(自适应辛普森)

    问题引入

         题目大意:给出n个圆,求这n个圆面积的并

         输入格式:

         第一行输入一个正整数 n 表示圆的个数 (1<=n<=1000)

         接下来n行,每行三个正整数 x , y, r,表示圆心的坐标和圆的半径

         输出格式:

         输出一个实数表示答案,保留三位小数

         这道题可以用自适应辛普森乱搞,这里先简单入门一下什么是自适应辛普森(Simpson)积分

    自适应辛普森

         定积分:

         定积分就是求f(x)在区间[a, b]中的图像面积,如下图:

                                     

          即阴影部分面积为: 

                

         但是大部分函数的原函数并不存在,或者求解过程极其复杂,因此可以使用自适应辛普森法递归求解近似值。

         辛普森公式:

                                      

           学习Simpson公式最好是要对积分有一定了解,但是不了解也没关系(像我这样高数忘光的人只能生搬硬套)

           如果感兴趣可以自己去推导

         

          自适应辛普森

           有了Simpson公式之后,我们便可以把区间划分为一段段的小区间,然后在计算求和

           通过递归函数integral(double L,double R),每一次递归将区间 [L, R] 分成 [L,mid]和[mid,R], 其中mid=(L+R)/2,再分别计算 f(x) 在区间[L,R]、[L,mid] 、[mid, R] 的积分Sum, Sl, Sr, 当Sum和Sl+Sr的误差满足题目的精度要求时,便可退出递归。

    double F(double x){    //相应的积分函数
        return (c*x+d)/(a*x+b);
    }
    double Simpson(double a,double b){   //辛普森公式
        double c=(a+b)/2;
        return (b-a)*(F(a)+F(b)+4*F(c))/6;
    }
    double integral(double L,double R){     //递归求解
        double mid=(L+R)/2;
        double S=Simpson(L,R),Sl=Simpson(L,mid),Sr=Simpson(mid,R);
        if(fabs(Sl+Sr-S)<eps)
           return Sl+Sr;
        else 
           return integral(L,mid)+integral(mid,R);
    }

         例题:

          poj4525

           https://www.luogu.com.cn/problem/P4525

           ac代码:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #include<vector>
    #include<queue>
    #include<stack>
    #include<map> 
    #define INF 0x3f3f3f3f
    using namespace std;
    typedef long long LL;
    typedef unsigned long long ULL;
    const int maxn=2e5+6;
    const int mod=1e9+7;
    const double pi=acos(-1.0);
    const double eps=1e-11;
    double a,b,c,d,l,r;
    double F(double x){
        return (c*x+d)/(a*x+b);
    }
    double Simpson(double a,double b){
        double c=(a+b)/2;
        return (b-a)*(F(a)+F(b)+4*F(c))/6;
    }
    double integral(double L,double R){
        double mid=(L+R)/2;
        double S=Simpson(L,R),Sl=Simpson(L,mid),Sr=Simpson(mid,R);
        if(fabs(Sl+Sr-S)<eps)
           return Sl+Sr;
        else 
           return integral(L,mid)+integral(mid,R);
    }
    int main(){
        scanf("%lf%lf%lf%lf%lf%lf",&a,&b,&c,&d,&l,&r);
        printf("%.6lf
    ",integral(l,r));
        return 0;
    }

       

      HDU1724 Ellipse

        http://acm.hdu.edu.cn/showproblem.php?pid=1724

       ac代码:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #include<vector>
    #include<queue>
    #include<stack>
    #include<map> 
    #define INF 0x3f3f3f3f
    using namespace std;
    typedef long long LL;
    typedef unsigned long long ULL;
    const int maxn=2e5+6;
    const int mod=1e9+7;
    const double pi=acos(-1.0);
    const double eps=1e-11;
    double a,b,l,r;
    double F(double x){
        return b*sqrt(1-x*x/(a*a));
    }
    double Simpson(double a,double b){
        double c=(a+b)/2;
        return (b-a)*(F(a)+F(b)+4*F(c))/6;
    }
    double integral(double L,double R){
        double mid=(L+R)/2;
        double S=Simpson(L,R),Sl=Simpson(L,mid),Sr=Simpson(mid,R);
        if(fabs(Sl+Sr-S)<eps)
           return Sl+Sr;
        else 
           return integral(L,mid)+integral(mid,R);
    }
    int main(){
        int t;
        scanf("%d",&t);
        while(t--){
             scanf("%lf%lf%lf%lf",&a,&b,&l,&r);
             printf("%.3lf
    ",2*integral(l,r));
        }
        return 0;
    }

      大概了解了自适应辛普森后,我们回到求解多个圆面积的交的问题上

                                 

      例如要求上图5个圆相交形成的红色部分的面积,我们可以将这些圆看成一个整体的函数,然后就可以用辛普森乱搞了。

      首先对于被其他圆包含的圆,对答案并不会产生贡献,因此我们可以先预处理,遍历一遍所有圆,将被其他圆包含的圆去除

    (圆已先按半径大小从小到大排序)

    void init(){    //预处理,删除被包含的圆 
        int cnt=0;
        int i,j;
        for(i=1;i<=n;i++){
            for(j=i+1;j<=n;j++){
                if(c[i].r-c[j].r+dis(c[i].o,c[j].o)<eps)     
                   break;
            }
            if(j>n) c[++cnt]=c[i];
        }
        n=cnt;
    }

        之后将圆按最左端点的x坐标(圆心x坐标减去半径即可,最右端则加上半径)从小到大排序

    bool cmp(cir a,cir b){
        return a.o.x-a.r<b.o.x-b.r;
    }

       接下来就可以辛普森乱搞了

       遍历所有圆,每找出一段连通的圆,便做一次自适应辛普森,累加答案即可

    (如图可以分成3段)

                                        

    void solve(){   //寻找每段联通的圆,进行计算 
        double l,r;
        int i=1,j;
        while(i<=n){
            l=c[i].o.x-c[i].r,r=c[i].o.x+c[i].r;
            for(j=i+1;j<=n;j++){
                if(c[j].o.x-c[j].r>r)  break;    //圆j最左端的x坐标比前面圆的最右端还大,说明不满足上述要求
                r=max(r,c[j].o.x+c[j].r);
            }
            st=i,ed=j-1,i=j;
            ans+=integral(l,r);
        }
    }

      接下来就是直接套上自适应辛普森的板子,这里最关键的就是已知 x 的值,怎么求它对应的 f(x) 

      若 x 值为 a,f(x)其实就求直线x=a, 被圆覆盖的长度,如下图红色实线部分

                                        

      所以我们可以求出直线x=a与所有圆的交点,做贪心覆盖,求其长度

    double F(double x){   //求F(x) 
        v.clear();
        for(int i=st;i<=ed;i++){   //处理出交点的纵坐标,保存为线段 
            if(x>c[i].o.x+c[i].r||x<c[i].o.x-c[i].r)
                continue;
             double l=x-c[i].o.x;
             l=sqrt(c[i].r*c[i].r-l*l);
             v.push_back((seg){c[i].o.y-l,c[i].o.y+l});
        } 
        if(v.empty())  
           return 0.00;
        sort(v.begin(),v.end());   //开始贪心覆盖 
        double s=0,last=v[0].l;
        for(int i=0;i<v.size();i++){
            if(v[i].r>last){
                s+=v[i].r-max(last,v[i].l);
                last=v[i].r;
            }
        }
        return s;
    }

      题目链接:https://www.luogu.com.cn/problem/SP8073

      ac代码:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<cmath>
    #include<vector>
    #include<queue>
    #include<stack>
    #include<map> 
    #define INF 1e16
    using namespace std;
    typedef long long LL;
    typedef unsigned long long ULL;
    const int maxn=2e5+6;
    const int mod=1e9+7;
    const double pi=acos(-1.0);
    const double eps=1e-7;
    struct Point{
        double x,y;
        Point(double x=0,double y=0):x(x),y(y) {} 
    }; 
    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 cir{
        Point o;
        double r;
        friend bool operator <(cir a,cir b){
            return a.r<b.r;
        }
    }c[maxn];
    double dis(Point a,Point b){
        return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
    } 
    
    int n;
    vector<seg>v;
    double ans=0;
    int st,ed;
    
    double F(double x){   //求F(x) 
        v.clear();
        for(int i=st;i<=ed;i++){   //处理出交点的纵坐标,保存为线段 
            if(x>c[i].o.x+c[i].r||x<c[i].o.x-c[i].r)
                continue;
             double l=x-c[i].o.x;
             l=sqrt(c[i].r*c[i].r-l*l);
             v.push_back((seg){c[i].o.y-l,c[i].o.y+l});
        } 
        if(v.empty())  
           return 0.00;
        sort(v.begin(),v.end());   //开始贪心覆盖 
        double s=0,last=v[0].l;
        for(int i=0;i<v.size();i++){
            if(v[i].r>last){
                s+=v[i].r-max(last,v[i].l);
                last=v[i].r;
            }
        }
        return s;
    }
    double Simpson(double a,double b){  //自适应辛普森 
        double c=(a+b)/2;
        return (b-a)*(F(a)+F(b)+4*F(c))/6;
    }
    double integral(double L,double R){
        double mid=(L+R)/2;
        double S=Simpson(L,R),Sl=Simpson(L,mid),Sr=Simpson(mid,R);
        if(fabs(Sl+Sr-S)<eps)
           return Sl+Sr;
        else 
           return integral(L,mid)+integral(mid,R);
    }
    
    void init(){    //预处理,删除被包含的圆 
        int cnt=0;
        int i,j;
        for(i=1;i<=n;i++){
            for(j=i+1;j<=n;j++){
                if(c[i].r-c[j].r+dis(c[i].o,c[j].o)<eps)
                   break;
            }
            if(j>n) c[++cnt]=c[i];
        }
        n=cnt;
    }
    bool cmp(cir a,cir b){
        return a.o.x-a.r<b.o.x-b.r;
    }
    void solve(){   //寻找每段联通的圆,进行计算 
        double l,r;
        int i=1,j;
        while(i<=n){
            l=c[i].o.x-c[i].r,r=c[i].o.x+c[i].r;
            for(j=i+1;j<=n;j++){
                if(c[j].o.x-c[j].r>r)  break;
                r=max(r,c[j].o.x+c[j].r);
            }
            st=i,ed=j-1,i=j;
            ans+=integral(l,r);
        }
    }
    int main(){
        scanf("%d",&n);
        double l=INF,r=-INF;
        for(int i=1;i<=n;i++){
            scanf("%lf%lf%lf",&c[i].o.x,&c[i].o.y,&c[i].r);
            l=min(l,c[i].o.x-c[i].r);
            r=max(r,c[i].o.x+c[i].r);
        }
        sort(c+1,c+1+n);
        init();
        sort(c+1,c+1+n,cmp);
        solve();
        printf("%.3lf
    ",ans);
        return 0; 
    }
  • 相关阅读:
    计算机组成原理学习总纲图
    USE RED
    既有的问题如何解决
    字符串极值题解
    扩展 KMP
    KMP
    FHQ-Treap
    STL
    iOS内存管理理论知识过一遍
    iOS中Block理论知识过一遍
  • 原文地址:https://www.cnblogs.com/wyy11/p/13049523.html
Copyright © 2011-2022 走看看