训练赛 blot

题面

题目大意

在地球和太阳之间有一个绕地心公转,绕其自身重心自转的干扰器。干扰器会遮挡阳光。我们将太阳视为一个点光源,地球视为一个圆,而干扰器是一个简单多边形。现在询问在某一时刻,地球上被阳光照射的区域的长度(即圆上被照射到的弧的长度).

输入格式

第一行五个整数, Xs,Ys,Xe,Ye,R,(Xs,Ys),(Xe,Ye),R.
第二行四个整数, n,T1,T2,T ,分别表示干扰器上有n个点,干扰器的公转周期,干扰器的自转周期以及询问的时刻。
接下来n行,每行两个整数x,y.按顺时针或逆时针顺序给出干扰器上的n个点.

输出格式

一行,一个实数表示答案,保留两位小数。

数据范围

n30R,T,T1,T2>0104

题解

其实就是一个模拟。不需要用到什么重要的算法,只是要注意一些细节问题.

  • 重心的求法?
  • 将多边形剖分成若干个三角形,每个三角形求一次重心。之后以三角形有向面积为权重,求这些重心的加权平均数即可。
    举个例子:求梯形A(0,0),B(0,12),C(12,12),D(24,0)的重心:
    1.剖分为三角形ABC与三角形ACD.
    2.重心分别为:(4,8)与(12,4).
    3.权重(有向面积):-72与-144
    4.重心坐标(x,y)满足:
    x=4(72)+12(144)(72)+(144)=283
    y=8(72)+4(144)(72)+(144)=163
  • 如果是凹多边形怎么办?
    • 这个没有关系,因为我们是用的有向面积。
  • 公转自转的处理?
    • 利用转轴公式。如果记不住的话,可以利用复平面知识现推:
      (x+yi)(cosϕ+isinϕ)=(xcosϕysinϕ)+(xsinϕ+ycosϕ)i,i.
    • 旋转的问题:
      • 先计算出各点与重心的相对位置,记作向量 pi
      • pi 按照规定角度旋转,得到 pi
      • 旋转重心 g
      • 计算g+pi即得到各点的位置.
      • 注意不要先绕重心旋转再绕地心旋转,那样干扰器上各点的自转角度其实变大了。
  • 覆盖问题的处理
    • 遍历所有点,以太阳为基准,按极角排序?
    • 是不是一定要排序?是否可以只取最大值与最小值?
      • atan2的值变化不连续,第二象限角和第三象限角对应的函数值有突变,故不能单纯地取最大值与最小值。
      • 但是我们可以事先将太阳转到x负半轴上,那么所有合法的角必然落在第一象限与第四象限。这样就可以简单地取最值了。
    • 注意地球背面的点不能算在内
    • 计算光线与地球交点的圆心角时,建议利用垂径定理,简化运算。
  • 精度问题:我们要相信cmath库中的函数都是算得很准的。额,其实这个就算是有精度差,我也无能为力。据说有大佬把地球分成了一百万个小段来处理,以取得更高的精度,这种做法我实在不会。

代码

变量名很乱

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const double pi=3.1415926535897932384626433832795;//这个可以用windows自带的计算器算
void _r(int& x,bool f=0)
{
    char c=getchar();
    while(c<'0'||c>'9')
    {
        f|=(c=='-');
        c=getchar();
    }
    for(x=0;c>='0'&&c<='9';c=getchar())
    {
        x=(x<<1)+(x<<3)+c-'0';
    }
    x=f?-x:x;
    return ;
}//输入优化
struct point//点,向量
{
    double x,y;
    point(double a=0,double b=0)
    {
        x=a;
        y=b;
    }
    point operator + (point b)
    {
        return point(x+b.x,y+b.y);
    }
    point operator - (point b)
    {
        return point(x-b.x,y-b.y);
    }
    double operator * (point b)
    {
        return x*b.y-y*b.x;
    }
    point rot(double A)
    {
        double si=sin(A),co=cos(A);
        return point(x*co-y*si,x*si+y*co);
    }//转轴,弧度制
    void print()
    {
        printf("%.2lf       %.2lf   \n",x,y);
    }//调试语句
};
inline double Area(point a,point b,point c)
{
    return (b-a)*(c-a);
}//有向面积
point earth,sun,p[50],cen;
double R;
int T1,T2,T;//公转,自转,time;       
int n,X,Y;
point cal()
{
    if(n==1)
    {
        return p[1];
    }
    if(n==2)
    {
        return point((p[1].x+p[2].x)/2.0,(p[1].y+p[2].y)/2.0);
    }
    double summ=0,_x=0,_y=0,_s=0,tx,ty;
    for(int i=2;i<n;i++)
    {
        _s=Area(p[1],p[i],p[i+1]);
        tx=(p[1].x+p[i].x+p[i+1].x)/3.0;
        ty=(p[1].y+p[i].y+p[i+1].y)/3.0;
        _x+=tx*_s;
        _y+=ty*_s;
        summ+=_s;
    }
    return point(_x/summ,_y/summ);
}//求重心
const double eps=1e-7;
double solve(double A)
{
    if(A<0)
    {
        return -pi/2.0-A+acos(sun.x*sin(A)/R);
    }
    else
    {
        return pi/2.0-A-acos(-sun.x*sin(A)/R);
    }
}//计算交点的圆心角,垂径定理
int main()
{
    //freopen("blot.in","r",stdin);
    //freopen("blot.out","w",stdout);
    _r(X);
    _r(Y);
    sun=point(1.0*X,1.0*Y);
    _r(X);
    _r(Y);
    earth=point(1.0*X,1.0*Y);
    sun=sun-earth;
    double angle=atan2(sun.y,sun.x);
    angle=pi-angle;
    sun=sun.rot(angle);
    _r(X);
    R=1.0*X;
    _r(n);
    scanf("%d%d%d",&T1,&T2,&T);
    for(int i=1;i<=n;i++)
    {
        _r(X);
        _r(Y);
        p[i]=point(1.0*X,1.0*Y);
        p[i]=p[i]-earth;
        p[i]=p[i].rot(angle);
    }
    cen=cal();
    int t1=T%T1,t2=T%T2;
    angle=2*pi*t2/(1.0*T2);
    point tmp;
    for(int i=1;i<=n;i++)
    {
        tmp=p[i]-cen;
        p[i]=tmp.rot(angle);
    }
    angle=2*pi*t1/(1.0*T1);
    cen=cen.rot(angle);
    for(int i=1;i<=n;i++)
    {
        p[i]=p[i]+cen;
    }
    double lim,cenA,mx,mn,S;
    cenA=asin(R/-sun.x);//阳光的范围,圆心角
    S=pi*R-2*cenA*R;//无遮挡情况下的照射弧长
    mx=-cenA;
    mn=cenA;
    lim=R*R/sun.x;
    for(int i=1;i<=n;i++)
    {
        if(p[i].x<lim)
        {
            tmp=p[i]-sun;
            angle=atan2(tmp.y,tmp.x);
            mx=max(mx,angle);
            mn=min(mn,angle);
        }
    }
    double L=0,a1,a2;
    mx=min(cenA,mx);
    mn=max(-cenA,mn);
    if(mx>mn)
    {
        a1=solve(mn);
        a2=solve(mx);
        L=(a2-a1)*R;
    }
    printf("%.2lf\n",S-L);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值