N圆面积并

【问题求解】
给定N 个圆形,求出其并集.

【算法分析】

PS.以下算法基于正方向为 逆时针


考虑上图中的蓝色圆,绿色的圆和蓝色的圆交于 A,B 2个交点 ,我们在逆时针系下考虑,那么 可以知道 对于蓝色的圆,它对应的某个  角度区间被覆盖了

假设 区间为 [A, B], 并且角度是按照 圆心到交点的 向量的 极角来定义 (为了方便,我一般都把角度转化到 [0,2pi]区间内) 那么可以知道在这种 标识情况下,可能存在以下情况

这种区间的跨度如何解决呢?实际上十分简单,只需要把[A,B] 拆成 [A, 2PI], [0,B]即可,也就是所谓的添加虚拟点



下面介绍一下 对于我们当前所求任务的实际运用( 利用上述做法)

首先 对于所给的N个圆,我们可以进行 去冗杂 ,表现为:
(1) 去掉 被包含 (内含 or 内切)的小圆 ()
(2) 去掉相同的圆

枚举一个圆,并对于剩下的圆和它求交点,对于所求的的交点,可以得到一个角度区间 [A,B], 当然区间如果跨越了( 例如 [1.5PI, 0.5PI],注意这里是有方向的 ) 2PI那么需要拆 区间 

可以知道, 最后区间的并集必然是最后 所有圆和当前圆的交集的一个边界!

于是我们得到互补区间(所谓互补区间就是[0,2PI]内除去区间并的区间,可能有多个)

 

假设我们先枚举了橙色的圆,那么得到了许多角度区间,可以知道绿色的和蓝色的角度区间是“未被覆盖的”,对于未被覆盖的

圆弧染色!

而对于其他圆,我们也做同样的步骤, 同时把他们未被覆盖的角度区间的圆弧标记为黑色阴影

于是最终的结果就如下图 (染色只考虑圆弧)




通过观察不难发现, 圆的并是许多的圆弧+ 许多多边形的面积之和 (注意这里为 简单多边形 ,并且面积有正负之别!)

于是我们累加互补区间的圆弧面积到答案,并把互补区间确定的弦的有向面积累加到答案

(例如在上面的例子中,我们在扫描到橙色圆的这一步只需要累加蓝色和绿色的圆弧面积 以及 蓝色和绿色的有向面积,注意这里蓝色和绿色的边必然是最后那个多边形的边!)


这里涉及到一个问题,就是:
圆弧可能大于一半的圆 ,例如上图中最大的圆,当然如果你推出了公式,那么实际上很容易发现 无论圆弧长啥样 都能算出正确的答案!
半径为R的圆中,弧度区间为K的圆弧的面积为 :  0.5 * r * r * (K - sin(K))

之后还是有一些疑惑,如果存在"洞"?


美妙之处在于此,由于面积 有方向 (正负),所以洞会被 自然抵消 !(可以类比计算简单多边形面积)

枚举 圆,并和其他圆求交,求得区间,排序
这里是O(n^2 logn)
至于区间扫描和累加的复杂度则是O(n)
于是复杂度
O(n * (nlogn + n) ) 

O(n^2 logn)

代码已实现,大约140行,和圆的交的思路十分类似,圆的交的思路请参考:

http://hi.baidu.com/aekdycoin/blog/item/12267a4e9476153bafc3abbd.html

 

圆并的题目:

http://www.spoj.pl/problems/CIRU/

 

https://www.spoj.pl/problems/VCIRCLES/

 

 

【圆并的数值积分】

// 先贴代码,稍后补上

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<cmath>
using namespace std;
const int maxn = 1005;
typedef double db;
const db EPS = 1e-6;
typedef pair<db, db> PDD;
int x[ maxn ], y[ maxn ], r[ maxn ];
int nx[ maxn ], ny[ maxn ], nr[ maxn ];
int xl[ maxn ], xr[ maxn ];

int s[ maxn ];
inline bool cmp( int a, int b) {
     if( x[ a ] - r [ a ] == x[ b ] - r [ b ] ) return x[ a ] + r[ a ] < x[ b ] + r [ b ];
     return x[ a ] - r[ a ] < x[ b ] - r [ b ];
}
inline bool cmp0(int a, int b){return r[ a ] > r [ b ];}
int n;
int L, R;
PDD se[ maxn ];
inline db f( db v){
   int sz = 0, i, j ;
   db ret = 0.0;
   for(i = L; i < R; ++ i){
        if( v <= xl[ i ] || v >= xr[ i ] ) continue;
        j = s[ i ];
        db d = sqrt(r[ j ]- (v - x [ j ]) * (v - x[ j ]));
        se[ sz ].first = y[ j ] - d;
        se[ sz ].second = y[ j ] +  d;
        ++ sz;   
   }
   sort( se, se + sz);
   for(i = 0; i < sz; ++ i){
         db nowl , nowr;
         nowl = se[ i ].first;
         nowr = se[ i ].second;
         for( j = i + 1; j < sz; ++ j) if(se[ j ].first > nowr) break;
         else nowr = max( nowr, se[ j ].second);
         ret += nowr - nowl;
         i = j - 1;      
   }
   return ret;
}
#define fs(x) ((x) < 0 ? (-(x)) : (x))
inline db rsimp( db l,db m, db r, db sl, db sm, db sr,db tot){
    db m1 = (l + m) * 0.5, m2 = (m + r) * 0.5;
    db s0 = f( m1), s2 = f( m2);
    db gl = (sl + sm + s0 + s0 + s0 + s0)*(m-l), gr = (sm + sr + s2 + s2 + s2 + s2)*(r-m);
    if( fs(gl + gr - tot) < EPS) return gl + gr;
    return rsimp( l, m1, m, sl, s0, sm, gl) + rsimp( m, m2,r, sm, s2, sr, gr);         
}

bool get(){ 
     if(1 != scanf("%d", &n)) return 0;
     int i, j = 0, k;
     for(i = 0; i < n; ++ i) scanf("%d%d%d", x + i, y + i, r + i), s[ i ] = i;
     sort( s, s + n, cmp0);
     for(i = 0; i < n; ++ i){
           for(k = 0; k < j; ++ k)
                 if( (nx [ k ] - x [s[i]]) * (nx [ k ] - x [s[i]])  + (ny [ k ] - y [s[i]]) *(ny [ k ] - y [s[i]])  
                     <= (nr[ k ] - r[ s[ i ] ]) * (nr[ k ] - r[ s[ i ] ]) ) break;
           if( k == j) {
               nx[ j ] = x[ s[ i ] ];
               ny[ j ] = y[ s[ i ] ];
               nr[ j ] = r[ s[ i ] ];
               s[ j ] = j;
               j ++;    
           }      
     }
     n = j;
     for(i = 0; i < n; ++ i) x[ i ] = nx[ i ], y[ i ] = ny[ i ], r[ i ] = nr[ i ];
     return 1;
}
  
void work(){
     sort( s, s + n, cmp) ;
     db lo, hi, ans = 0.0;
     int i, j;
     for(i = 0; i < n; ++ i) xl[ i ] = x[ s[ i ] ] - r[ s[ i ] ], xr[ i ] = x[ s[ i ] ] + r[ s[ i ] ], r[ s[i] ] *= r[ s[i] ];
     for(i = 0; i < n; ++ i){
           int ilo, ihi;
           ilo = xl[ i ];
           ihi = xr[ i ];
           for(j = i + 1; j < n; ++ j) {
                 if( xl[ j ] > ihi ) break;
                 ihi = max( ihi, xr[ j ]);
           }
           db lo = ilo;
           db hi = ihi;      
           L = i;
           R = j;
           db mid = (lo + hi) * 0.5;
           db sl = f(lo), sm = f(mid), sr = f(hi);
           db tot = sl + sr + sm + sm + sm + sm;
           ans += rsimp( lo, mid , hi, sl, sm , sr, tot );
           i = j - 1;
     }
     printf("%.3f\n", ans / 6.0);
}
  
int main(){
    while( get() ) work();
    return 0;
}

【圆并的扩展算法】

https://www.spoj.pl/problems/CIRUT/

 

【题目大意】

给出N个不同的圆,求出被覆盖恰好K次的面积,并顺序输出

 

【思路提示】

参考圆并和圆交的思路, 就是把问题转化为区间来考虑

同时请看下面的图:

上面的图,是对于被覆盖恰好2次的区域的边界连接起来得到的图形,我们可以发现什么呢?

(就不剧透了~~~~)

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值