首先写下格林公式,省略了公式成立的条件。
∬ 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(∂x∂Q−∂y∂P)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=21∬D(∂x∂Q−∂y∂P)dxdy=21∮xdy−ydx
考虑重心坐标的公式,分别有
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=∬Ddxdy∬Dxdxdy,yg=∬Ddxdy∬Dydxdy
为了方便起见,将分子分别记作 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(∂x∂Q−∂y∂P)dxdy=∬Dxdxdy=∮C21x2dy
同理可得:
z y = − 1 2 ∮ C y 2 d x z_y=-{\frac{1}{2}}\oint_C{y^2dx} zy=−21∮Cy2dx
显然圆并的边界均是圆弧,因此可以描述为:
{ 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=∮Cxdy−ydx=∮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=21∮C(x02+2rx0cosθ+r2cos2θ)d(y0+rsinθ)=2r∮Cx02dsinθ+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=−21∮Cy2dx=−21∮C(y02+2ry0sinθ+r2sin2θ)d(rcosθ)=−2r∮Cy02dcosθ+2ry0dsin2θ−ry0dθ+r2dcosθ−3r2dcos3θ=2r(3r2cos3θ+ry0θ−(r2+y02)cosθ−2ry0sin2θ)∣αβ
所以只需求出圆并的每一段边界圆弧,计算即可。
最多有1000个圆,求并的面积。被覆盖的圆要剔除,不能参与运算。退化的圆也可以剃掉。简单而言就是对每一个圆,求其他圆覆盖该圆的圆弧角度区间 [ α , β ] [\alpha, \beta] [α,β],最多有 N − 1 N-1 N−1段区间,然后对这些区间排序,对不在这些区间范围内的区间引用上述公式进行计算即可。时间复杂度为 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;
}