格林公式求圆并的面积及重心

格林公式求圆并的面积及重心

  • 格林公式
    • 格林公式对面积及重心的推导
    • 圆并的面积与重心计算
  • [SPOJ CIRU圆并的面积](https://www.spoj.com/problems/CIRU/)
  • 圆并的重心

格林公式

首先写下格林公式,省略了公式成立的条件。

∬ D ( ∂ Q ∂ x − ∂ P ∂ y ) d x d y = ∮ P d x + Q d y \iint_D\big(\frac{\partial{Q}}{\partial{x}}-\frac{\partial{P}}{\partial{y}}\big)dxdy=\oint{Pdx+Qdy} D(xQyP)dxdy=Pdx+Qdy

其中 D D D是一个面, L L L是其边缘曲线。借助格林公式可以将面积分转换为一维的路径积分。 P P P Q Q Q是关于 x , y x,y x,y的二元函数。

格林公式对面积及重心的推导

考虑面积公式: s = ∬ D d x d y s=\iint_D{dxdy} s=Ddxdy,套用格林公式,令 Q = x , P = − y Q=x,P=-y Q=x,P=y,则有

s = ∬ D d x d y = 1 2 ∬ D ( ∂ Q ∂ x − ∂ P ∂ y ) d x d y = 1 2 ∮ x d y − y d x s=\iint_D{dxdy}=\frac{1}{2}\iint_D\big(\frac{\partial{Q}}{\partial{x}}-\frac{\partial{P}}{\partial{y}}\big)dxdy=\frac{1}{2}\oint{xdy-ydx} s=Ddxdy=21D(xQyP)dxdy=21xdyydx

考虑重心坐标的公式,分别有

x g = ∬ D x d x d y ∬ D d x d y , y g = ∬ D y d x d y ∬ D d x d y x_g=\frac{\iint_D{xdxdy}}{\iint_Ddxdy}, y_g=\frac{\iint_D{ydxdy}}{\iint_Ddxdy} xg=DdxdyDxdxdy,yg=DdxdyDydxdy

为了方便起见,将分子分别记作 z x , z y z_x,z_y zx,zy。首先考虑 z x z_x zx,令 Q = 1 2 x 2 , P = 0 Q=\frac{1}{2}x^2,P=0 Q=21x2,P=0,则

z x = ∬ D ( ∂ Q ∂ x − ∂ P ∂ y ) d x d y = ∬ D x d x d y = ∮ C 1 2 x 2 d y z_x=\iint_D\big(\frac{\partial{Q}}{\partial{x}}-\frac{\partial{P}}{\partial{y}}\big)dxdy = \iint_D{xdxdy} = \oint_C{\frac{1}{2}x^2}dy zx=D(xQyP)dxdy=Dxdxdy=C21x2dy

同理可得:

z y = − 1 2 ∮ C y 2 d x z_y=-{\frac{1}{2}}\oint_C{y^2dx} zy=21Cy2dx

圆并的面积与重心计算

显然圆并的边界均是圆弧,因此可以描述为:

{ x = x 0 + r c o s θ y = y 0 + r s i n θ   α ≤ θ ≤ β \left\{ \begin{aligned} x & = x_0+rcos\theta \\ y & = y_0 + rsin\theta \end{aligned} \right. \, {\rm}{\rm} \alpha\le\theta\le\beta {xy=x0+rcosθ=y0+rsinθαθβ

分别代入上述公式,可以求得:

s = ∮ C x d y − y d x = ∮ C ( x 0 + r c o s θ ) d ( y 0 + r s i n θ ) − ( y 0 + r s i n θ ) d ( x 0 + r c o s θ ) = ∮ C r x 0 d s i n θ + r 2 c o s 2 θ d θ − r y 0 d c o s θ + r 2 s i n 2 θ d θ = r ( x 0 s i n θ − y 0 c o s θ + r θ ) ∣ α β s=\oint_C{xdy-ydx}=\oint_C{(x_0+rcos\theta)d(y_0+rsin\theta)}-(y_0+rsin\theta)d(x_0+rcos\theta) \\ = \oint_C{rx_0dsin\theta+r^2cos^2{\theta}d\theta-ry_0dcos\theta+r^2sin^2{\theta}d\theta} \\ = r(x_0sin\theta-y_0cos\theta+r\theta)|_\alpha^\beta s=Cxdyydx=C(x0+rcosθ)d(y0+rsinθ)(y0+rsinθ)d(x0+rcosθ)=Crx0dsinθ+r2cos2θdθry0dcosθ+r2sin2θdθ=r(x0sinθy0cosθ+rθ)αβ

z x = ∮ C 1 2 x 2 d y = 1 2 ∮ C ( x 0 2 + 2 r x 0 c o s θ + r 2 c o s 2 θ ) d ( y 0 + r s i n θ ) = r 2 ∮ C x 0 2 d s i n θ + 2 r x 0 c o s 2 θ d θ + r 2 c o s 3 θ d θ = r 2 ( ( x 0 2 + r 2 ) s i n θ + r x 0 θ + r x 0 s i n 2 θ 2 − r 2 s i n 3 θ 3 ) ∣ α β z_x = \oint_C{\frac{1}{2}x^2}dy = \frac{1}{2}\oint_C{(x_0^2+2rx_0cos\theta+r^2cos^2\theta)d(y_0 + rsin\theta)} \\ = \frac{r}{2}\oint_C{x_0^2dsin\theta+2rx_0cos^2{\theta}d\theta+r^2cos^3{\theta}d\theta} \\ =\frac{r}{2}\big((x_0^2+r^2)sin\theta+rx_0\theta+\frac{rx_0sin2{\theta}}{2}-\frac{r^2sin^3\theta}{3}\big)|_\alpha^\beta zx=C21x2dy=21C(x02+2rx0cosθ+r2cos2θ)d(y0+rsinθ)=2rCx02dsinθ+2rx0cos2θdθ+r2cos3θdθ=2r((x02+r2)sinθ+rx0θ+2rx0sin2θ3r2sin3θ)αβ

z y = − 1 2 ∮ C y 2 d x = − 1 2 ∮ C ( y 0 2 + 2 r y 0 s i n θ + r 2 s i n 2 θ ) d ( r c o s θ ) = − r 2 ∮ C y 0 2 d c o s θ + r y 0 2 d s i n 2 θ − r y 0 d θ + r 2 d c o s θ − r 2 3 d c o s 3 θ = r 2 ( r 2 c o s 3 θ 3 + r y 0 θ − ( r 2 + y 0 2 ) c o s θ − r y 0 s i n 2 θ 2 ) ∣ α β z_y=-{\frac{1}{2}}\oint_C{y^2dx}=-{\frac{1}{2}}\oint_C{(y_0^2+2ry_0sin\theta+r^2sin^2\theta)d(rcos\theta)} \\ = -{\frac{r}{2}}\oint_C{y_0^2dcos\theta+\frac{ry_0}{2}dsin2\theta-ry_0d\theta+r^2dcos\theta-\frac{r^2}{3}dcos^3\theta} \\ =\frac{r}{2}\big(\frac{r^2cos^3\theta}{3}+ry_0\theta-(r^2+y_0^2)cos\theta-\frac{ry_0sin2\theta}{2}\big)|_\alpha^\beta zy=21Cy2dx=21C(y02+2ry0sinθ+r2sin2θ)d(rcosθ)=2rCy02dcosθ+2ry0dsin2θry0dθ+r2dcosθ3r2dcos3θ=2r(3r2cos3θ+ry0θ(r2+y02)cosθ2ry0sin2θ)αβ

所以只需求出圆并的每一段边界圆弧,计算即可。

SPOJ CIRU圆并的面积

最多有1000个圆,求并的面积。被覆盖的圆要剔除,不能参与运算。退化的圆也可以剃掉。简单而言就是对每一个圆,求其他圆覆盖该圆的圆弧角度区间 [ α , β ] [\alpha, \beta] [α,β],最多有 N − 1 N-1 N1段区间,然后对这些区间排序,对不在这些区间范围内的区间引用上述公式进行计算即可。时间复杂度为 O ( N 2 l o g N ) O(N^2logN) O(N2logN)

#include 
using namespace std;

char *__abc147, *__xyz258, __ma369[100000];
#define __hv007() ((__abc147==__xyz258) && (__xyz258=(__abc147=__ma369)+fread(__ma369,1,100000,stdin),__abc147==__xyz258) ? EOF : *__abc147++)

int getInt(){
	int sgn = 1;
	char ch = __hv007();
	while( ch != '-' && ( ch < '0' || ch > '9' ) ) ch = __hv007();
	if ( '-' == ch ) {sgn = 0;ch=__hv007();}

	int ret = (int)(ch-'0');
	while( '0' <= (ch=__hv007()) && ch <= '9' ) ret = ret * 10 + (int)(ch-'0');
	return sgn ? ret : -ret;
}

#ifndef ONLINE_JUDGE
int const SIZE = 7;
#else
int const SIZE = 2010;
#endif

using Real = double;
using vi = vector<int>;
using vvi = vector<vi>;
using pii = pair<Real, Real>;
using vpii = vector<pii>;

Real const EPS = 1E-8;
Real const PI = acos(-1.0);
inline int sgn(Real x){return x>EPS?1:(x<-EPS?-1:0);}
inline bool is0(Real x){return 0 == sgn(x);} 
inline Real myacos(Real x){
    if(x > 1) x = 1.0;
    if(x < -1) x = -1.0;
    return acos(x);
}

struct Point{
    Real x, y;
    Real dist(const Point &b)const{
        Real u = x - b.x, v = y - b.y;
        return sqrt(u*u+v*v);
    }
};

struct Circle{
    Point center;
    Real radius;
    /// 两个圆相交,返回圆弧角度的区间,可能是一段,也可能是两段
    /// 角度范围为[-Pi, Pi)
    vpii inter(const Circle &b)const{
        Real d123 = center.dist(b.center);
        Real u111 = fabs(radius - b.radius);
        Real u222 = radius + b.radius;
        assert(sgn(u222-d123) > 0 && sgn(d123 - u111) > 0);

        Real cosvalue = (radius * radius + d123 * d123 - b.radius * b.radius) / (2.0 * radius * d123);
        Real jiao = myacos(cosvalue);
        Real anchor = atan2(b.center.y - center.y, b.center.x - center.x);
        Real from = anchor - jiao, to = anchor + jiao;

        vpii ans;
        if(sgn(to - PI) >= 0){ // 分两段
            ans.emplace_back(-PI, to-PI-PI);
            ans.emplace_back(from, PI);
            return ans;
        }
        if(sgn(from+PI) < 0){ // 分两段
            ans.emplace_back(-PI, to);
            ans.emplace_back(from+PI+PI, PI);
            return ans;
        }
        ans.emplace_back(from, to);
        return ans;
    }
};

Real cross(const Point &O, const Point &A, const Point &B){
    Real xoa = A.x - O.x, yoa = A.y - O.y;
    Real xob = B.x - O.x, yob = B.y - O.y;
    return xoa * yob - xob * yoa;
}

/// 原函数求差值
inline Real diff(Real (*f)(const Circle&, Real), const Circle&c, Real alpha, Real beta){
    return  f(c, beta) - f(c, alpha); 
}

/// 求面积的函数,缺一个0.5的因子
Real f(const Circle &c, Real theta){
    return c.radius * (c.radius * theta + c.center.x * sin(theta) - c.center.y * cos(theta));
}

/// 求圆并,索引从0开始
Real unite_circles(const Circle c[], int n){
    /// 首先求各圆相交的情况
    vvi status(n, vi());
    vi han(n, 0);
    for(int i=0;i<n;++i){
        if(han[i]) continue; // i被包含
        auto ci = c + i;
        if(is0(ci->radius)){
            han[i] = 1; continue;
        }

        for(int j=i+1;j<n;++j){
            auto cj = c + j;
            if(is0(cj->radius)){
                han[j] = 1; continue;
            }
            Real u = ci->center.dist(cj->center);
            Real v1 = fabs(ci->radius - cj->radius);
            Real v2 = ci->radius + cj->radius;
            if(sgn(v1-u) >= 0){ // 包含
                if(sgn(ci->radius - cj->radius) >= 0){
                    han[j] = 1; // j被包含
                }else{
                    han[i] = 1; // i被j包含,i就不用再算了
                    break;
                }
            }else if(sgn(v2-u) > 0){ // 相交
                status[i].push_back(j);
                status[j].push_back(i);
            }
        }        
    }
    /// 处理每个圆
    Real ans = 0;
    vpii vec;
    for(int i=0;i<n;++i){
        if(han[i]) continue; // 说明是被包含的
        const Circle * ci = c + i;
        const vi & st = status[i];
        if(st.empty()){ // 说明整个圆都是边界
            ans += diff(f, *ci, -PI, PI);
            continue;
        }
        vec.clear(); 
        vec.reserve(st.size());
        /// 对每个相交的圆求相交区间
        for(int j: st){
            auto tmp = ci->inter(c[j]);
            vec.insert(vec.end(), tmp.begin(), tmp.end());
        }
        /// 相交区间排序
        sort(vec.begin(), vec.end());
        /// 对每一段露在外面的圆弧做积分
        Real left = -PI;
        for(const auto &p: vec){
            if(left < p.first){ // 说明有一段边缘
                ans += diff(f, *ci, left, p.first);  
                left = p.second;
            }else if(left < p.second){ // 更新边界的起点
                left = p.second;
            }
        }
        /// 最后再加上一段,无论有没有
        ans += diff(f, *ci, left, PI);
    }
    return 0.5 * ans; // 最后乘0.5
}

int N;
Circle C[SIZE];

Real proc(){
    Real ans = unite_circles(C, N);
    return ans;
}

int main(){
#ifndef ONLINE_JUDGE
    freopen("1.txt", "r", stdin);
#endif
    N = getInt();
    for(int i=0;i<N;++i) C[i].center.x = getInt(), C[i].center.y = getInt(), C[i].radius = getInt();
    printf("%.3f\n", proc());
    return 0;
}

圆并的重心

Battle Mage,题目大意是:给定一个凸多边形形状的薄木板,上面有很多圆洞,圆洞可能会重叠。薄木板初始位于竖直平面内,给定每个点的坐标,给定圆洞的圆心和半径。现在将薄木板的第V个点固定住,然后让其在重力作用下达到平衡位置,问此时凸多边形的N个顶点的坐标。只需求出重心坐标,再做一个坐标变换即可。首先可以求出完整凸多边形的重心,再可以求出圆并的重心,最后可以求出薄木板的重心坐标。

#include 
using namespace std;

char *__abc147, *__xyz258, __ma369[100000];
#define __hv007() ((__abc147==__xyz258) && (__xyz258=(__abc147=__ma369)+fread(__ma369,1,100000,stdin),__abc147==__xyz258) ? EOF : *__abc147++)

int getInt(){
	int sgn = 1;
	char ch = __hv007();
	while( ch != '-' && ( ch < '0' || ch > '9' ) ) ch = __hv007();
	if ( '-' == ch ) {sgn = 0;ch=__hv007();}

	int ret = (int)(ch-'0');
	while( '0' <= (ch=__hv007()) && ch <= '9' ) ret = ret * 10 + (int)(ch-'0');
	return sgn ? ret : -ret;
}

#ifndef ONLINE_JUDGE
int const SIZE = 7;
#else
int const SIZE = 2010;
#endif

using Real = double;
using vi = vector<int>;
using vvi = vector<vi>;
using pii = pair<Real, Real>;
using vpii = vector<pii>;

Real const EPS = 1E-8;
Real const PI = acos(-1.0);
inline int sgn(Real x){return x>EPS?1:(x<-EPS?-1:0);}
inline bool is0(Real x){return 0 == sgn(x);} 
inline Real sqr(Real x){return x * x;}
inline Real cub(Real x){return x * x * x;}
inline Real myacos(Real x){
    if(x > 1) x = 1.0;
    if(x < -1) x = -1.0;
    return acos(x);
}

struct Point{
    Real x, y;
    Real dist(const Point &b)const{
        Real u = x - b.x, v = y - b.y;
        return sqrt(u*u+v*v);
    }
};

struct Circle{
    Point center;
    Real radius;
    /// 两个圆相交,返回圆弧角度的区间,可能是一段,也可能是两段
    /// 角度范围为[-Pi, Pi)
    vpii inter(const Circle &b)const{
        Real d123 = center.dist(b.center);
        Real u111 = fabs(radius - b.radius);
        Real u222 = radius + b.radius;
        assert(sgn(u222-d123) > 0 && sgn(d123 - u111) > 0);

        Real cosvalue = (radius * radius + d123 * d123 - b.radius * b.radius) / (2.0 * radius * d123);
        Real jiao = myacos(cosvalue);
        Real anchor = atan2(b.center.y - center.y, b.center.x - center.x);
        Real from = anchor - jiao, to = anchor + jiao;

        vpii ans;
        if(sgn(to - PI) >= 0){ // 分两段
            ans.emplace_back(-PI, to-PI-PI);
            ans.emplace_back(from, PI);
            return ans;
        }
        if(sgn(from+PI) < 0){ // 分两段
            ans.emplace_back(-PI, to);
            ans.emplace_back(from+PI+PI, PI);
            return ans;
        }
        ans.emplace_back(from, to);
        return ans;
    }
};

Real cross(const Point &O, const Point &A, const Point &B){
    Real xoa = A.x - O.x, yoa = A.y - O.y;
    Real xob = B.x - O.x, yob = B.y - O.y;
    return xoa * yob - xob * yoa;
}

/// 原函数求差值
inline Real diff(Real (*f)(const Circle&, Real), const Circle&c, Real alpha, Real beta){
    return  f(c, beta) - f(c, alpha); 
}

/// 求面积的函数,缺一个0.5的因子
Real f(const Circle &c, Real theta){
    return c.radius * (c.radius * theta + c.center.x * sin(theta) - c.center.y * cos(theta));
}

/// 求重心的x坐标的原函数,缺一个0.5的因子
Real fx(const Circle &c, Real theta){
    Real sint = sin(theta);
    Real r2 = sqr(c.radius);
    return c.radius * ((sqr(c.center.x)+r2)*sint + c.radius*c.center.x*(theta+0.5*sin(theta+theta)) - r2*cub(sint)/3.0);
}

/// 求重心的y坐标的原函数,缺一个0.5的因子
Real fy(const Circle &c, Real theta){
    Real cost = cos(theta);
    Real r2 = sqr(c.radius);
    return c.radius * (r2*cub(cost)/3.0 + c.radius*c.center.y*(theta-0.5*sin(theta+theta)) - (r2+sqr(c.center.y))*cost);
}

/// 求圆并,索引从0开始
tuple<Real, Real, Real> unite_circles(const Circle c[], int n){
    /// 首先求各圆相交的情况
    vvi status(n, vi());
    vi han(n, 0);
    for(int i=0;i<n;++i){
        if(han[i]) continue; // i被包含
        auto ci = c + i;
        if(is0(ci->radius)){
            han[i] = 1; continue;
        }

        for(int j=i+1;j<n;++j){
            auto cj = c + j;
            if(is0(cj->radius)){
                han[j] = 1; continue;
            }
            Real u = ci->center.dist(cj->center);
            Real v1 = fabs(ci->radius - cj->radius);
            Real v2 = ci->radius + cj->radius;
            if(sgn(v1-u) >= 0){ // 包含
                if(sgn(ci->radius - cj->radius) >= 0){
                    han[j] = 1; // j被包含
                }else{
                    han[i] = 1; // i被j包含,i就不用再算了
                    break;
                }
            }else if(sgn(v2-u) > 0){ // 相交
                status[i].push_back(j);
                status[j].push_back(i);
            }
        }        
    }
    /// 处理每个圆
    Real ans = 0, ansx = 0, ansy = 0;
    vpii vec;
    for(int i=0;i<n;++i){
        if(han[i]) continue; // 说明是被包含的
        const Circle * ci = c + i;
        const vi & st = status[i];
        if(st.empty()){ // 说明整个圆都是边界
            ans += diff(f, *ci, -PI, PI);
            ansx += diff(fx, *ci, -PI, PI);
            ansy += diff(fy, *ci, -PI, PI);
            continue;
        }
        vec.clear(); 
        vec.reserve(st.size());
        /// 对每个相交的圆求相交区间
        for(int j: st){
            auto tmp = ci->inter(c[j]);
            vec.insert(vec.end(), tmp.begin(), tmp.end());
        }
        /// 相交区间排序
        sort(vec.begin(), vec.end());
        /// 对每一段露在外面的圆弧做积分
        Real left = -PI;
        for(const auto &p: vec){
            if(left < p.first){ // 说明有一段边缘
                ans += diff(f, *ci, left, p.first);  
                ansx += diff(fx, *ci, left, p.first);
                ansy += diff(fy, *ci, left, p.first);
                left = p.second;
            }else if(left < p.second){ // 更新边界的起点
                left = p.second;
            }
        }
        /// 最后再加上一段,无论有没有
        ans += diff(f, *ci, left, PI);
        ansx += diff(fx, *ci, left, PI);
        ansy += diff(fy, *ci, left, PI);
    }
    return {ans, ansx, ansy}; // 都不用0.5,会抵消
}

int N, C, V;
Circle Cir[SIZE];
Point P[SIZE];

void proc(){
    /// 求圆并的重心
    auto ans = unite_circles(Cir, C);
    /// 求多边形的重心和面积
    Real cgx = 0, cgy = 0, area = 0;
    for(int i=2;i<N;++i){
        Real tmp = cross(P[0], P[i-1], P[i]);
        area += tmp;
        cgx += tmp * (P[0].x+P[i-1].x+P[i].x)/3.0;
        cgy += tmp * (P[0].y+P[i-1].y+P[i].y)/3.0;
    }
    /// 求薄木板的重心
    Real leftarea = area - get<0>(ans);
    Real leftx = (cgx - get<1>(ans)) / leftarea;
    Real lefty = (cgy - get<2>(ans)) / leftarea;
    /// 凸多边形做一个坐标平移
    Point origin = P[V];
    for(int i=0;i<N;++i) P[i].x -= origin.x, P[i].y -= origin.y;
    leftx -= origin.x, lefty -= origin.y;
    /// 旋转
    Real theta = atan2(lefty, leftx);
    theta = -0.5 * PI - theta;
    Real ca = cos(theta), sa = sin(theta);
    for(int i=0;i<N;++i){
        Real x = P[i].x, y = P[i].y;
		P[i].x = x * ca - y * sa + origin.x;
		P[i].y = y * ca + x * sa + origin.y; 
    }
}

int main(){
#ifndef ONLINE_JUDGE
    freopen("1.txt", "r", stdin);
#endif
    N = getInt(); C = getInt(); V = getInt() - 1;
    for(int i=0;i<N;++i) P[i].x = getInt(), P[i].y = getInt();
    for(int i=0;i<C;++i) Cir[i].center.x = getInt(), Cir[i].center.y = getInt(), Cir[i].radius = getInt();
    proc();
    for(int i=0;i<N;++i) printf("%.7f %.7f\n", P[i].x, P[i].y);
    return 0;
}

你可能感兴趣的:(ACM高等数学,ACM,算法)