计算几何(草稿)

计算几何

向量

加减 a=(xa,ya),b=(xb,yb) a+b=(xa+xb,ya+yb),ab=(xaxb,yayb)
点乘 ab=|a||b|cos<a,b>=(xaxb+yayb)
叉乘 a×b=(xaybxbya)
垂直 若两向量的点积为 0 ,则两向量垂直
平行 若两向量的叉积为 0 ,则两向量平行

struct P
{
    double x,y;
    P(double x=0,double y=0):x(x),y(y){};
}
P operator +(P a,P b){return P(a.x+b.x,a.y+b.y);}
P operator -(P a,P b){return P(a.x-b.x,a.y-b.y);}
double dot(P a,P b){return a.x*b.x+a.y*b.y;}
double cross(P a,P b){return a.x*b.y-a.y*b.x;}
double dis(P a,P b){return std::sqrt(cross(b-a,b-a));}

线段

表示方法:记录两端点,根据需要记录极角

struct L
{
    P l,r;
    double ang;
    L(P l=P(),P r=P()):l(l),r(r)
    {ang=std::atan2(r.y-l.y,r.x-l.x);};
};

求交点

P inter(L a,L b)
{
     double s1=cross(b.l-a.l,a.r-a.l),
            s2=cross(a.r-a.l,b.r-a.l);
     return P((b.l.x*s2+b.r.x*s1)/(s1+s2),
             (b.l.y*s2+b.r.y*s1)/(s1+s2));
}

表示:记录圆心和半径

struct C
{
    P cen;double r;
    C(P cen=P(),double r=0):cen(cen),r(r){};
};

两圆间的位置关系

  • 内切
  • 外切
  • 包含
  • 相交
  • 相离

两圆相交的弧度

求两圆的交点计算繁琐,而我们却可以很简单地知道两圆交点对各自的弧度。
若 a 和 b 两圆相交,那么对于

void inter(C a,C b)
{
    double d=dis(a.cen,b.cen),
           ang0=atan2(a.cen.x-b.cen.x,a.cen.y-b.cen.y),
           x=(a.r*a.r-b.r*b.r+d*d)/(2*d),
           angDet=acos(x/a.r);
    ang0-angDet,ang0+angDet
}

[BZOJ1043]下落的圆盘

有n个圆盘从天而降,后面落下的可以盖住前面的。求最后形成的封闭区域的周长。看下面这副图, 所有的红色线条的总长度即为所求.
1

/**************************************************************
    Problem: 1043
    User: zhangche0526
    Language: C++
    Result: Accepted
    Time:292 ms
    Memory:1344 kb
****************************************************************/

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
const int MAXN=1e3+5;
const double INF=1e60,PI=acos(-1);

int n;

struct P
{
    double x,y;
    P(double x=0,double y=0):x(x),y(y){};
};
P operator +(P a,P b){return P(a.x+b.x,a.y+b.y);}
P operator -(P a,P b){return P(a.x-b.x,a.y-b.y);}
double cross(P a,P b){return a.x*b.x+a.y*b.y;}
double dis(P a,P b){return std::sqrt(cross(b-a,b-a));}

struct C
{
    P cen;double r;
    C(P cen=P(),double r=0):cen(cen),r(r){};
} cir[MAXN];
struct R{double l,r;R(double l=0,double r=0):l(l),r(r){};} ran[MAXN];int rcnt;
bool cmp(const R &a,const R &b){return a.l<b.l;}
inline bool cntn(C a,C b){return a.r>=b.r+dis(a.cen,b.cen);}//a contains b
void inter(C a,C b)
{
    double d=dis(a.cen,b.cen),
           ang0=atan2(a.cen.x-b.cen.x,a.cen.y-b.cen.y),
           x=(a.r*a.r-b.r*b.r+d*d)/(2*d),
           angDet=acos(x/a.r);
    ran[++rcnt]=R(ang0-angDet,ang0+angDet);
}
double calc(int x)
{
    int i;
    for(i=x+1;i<=n;i++)
        if(cntn(cir[i],cir[x])) return 0;
    rcnt=0;
    for(i=x+1;i<=n;i++)
        if(!cntn(cir[x],cir[i])&&cir[x].r+cir[i].r>dis(cir[x].cen,cir[i].cen))
            inter(cir[x],cir[i]);
    double sum=0,now=0;
    for(i=1;i<=rcnt;i++)
    {
        if(ran[i].l<0) ran[i].l+=2*PI;
        if(ran[i].r<0) ran[i].r+=2*PI;
        if(ran[i].l>ran[i].r) ran[++rcnt]=R(0,ran[i].r),ran[i].r=2*PI;
    }
    std::sort(ran+1,ran+rcnt+1,cmp);
    for(i=1;i<=rcnt;i++)
        if(ran[i].l>now)
            sum+=ran[i].l-now,now=ran[i].r;
        else now=std::max(now,ran[i].r);
    sum+=2*PI-now;
    return sum*cir[x].r;
}

int main()
{
    int i;
    scanf("%d",&n);
    for(i=1;i<=n;i++)
    {
        double x,y,r;scanf("%lf%lf%lf",&r,&x,&y);
        cir[i]=C(P(x,y),r);
    }
    double ans=0;
    for(i=1;i<=n;i++) ans+=calc(i);
    printf("%.3lf\n",ans);
}

凸包

给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边型,它能包含点集中所有的点。
直接看裸题:

[COGS896]圈奶牛

农夫约翰想要建造一个围栏用来围住他的奶牛,可是他资金匮乏。他建造的围栏必须包括他的奶牛喜欢吃草的所有地点。对于给出的这些地点的坐标,计算最短的能够围住这些点的围栏的长度。
地点数 N104

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>

const int MAXN=1e4+5;
int n;

struct P
{
    double x,y;
    P(double x=0,double y=0):x(x),y(y){};
} pt[MAXN];
P operator +(P a,P b){return P(a.x+b.x,a.y+b.y);}
P operator -(P a,P b){return P(a.x-b.x,a.y-b.y);}
double cross(P a,P b){return a.x*b.y-a.y*b.x;}
double dot(P a,P b){return a.x*b.x+a.y*b.y;}
double dis(P a,P b){return sqrt(dot(b-a,b-a));}

P ch[MAXN];int ccnt;
bool cmp(const P &a,const P &b)
{return a.x<b.x||(a.x==b.x&&a.y<b.y);}

void calCH()
{
    int i;
    std::sort(pt+1,pt+n+1,cmp);
    for(i=1;i<=n;i++)
    {
        while(ccnt>1&&cross(ch[ccnt]-ch[ccnt-1],pt[i]-ch[ccnt-1])<=0)
            ccnt--;
        ch[++ccnt]=pt[i];
    }
    int tmp=ccnt;
    for(i=n-1;i;i--) 
    {
        while(ccnt>tmp&&cross(ch[ccnt]-ch[ccnt-1],pt[i]-ch[ccnt-1])<=0)
            ccnt--;
        ch[++ccnt]=pt[i];
    }
    if(n>1) ccnt--;
}

int main()
{
    freopen("fc.in","r",stdin);
    freopen("fc.out","w",stdout);
    int i;
    scanf("%d",&n);
    for(i=1;i<=n;i++)
    {
        double x,y;scanf("%lf%lf",&x,&y);
        pt[i]=P(x,y);
    }
    calCH();
    double ans=0;
    for(i=1;i<=ccnt;i++) ans+=dis(ch[i],ch[i+1]);
    printf("%.2lf\n",ans);
    return 0;
}

[POJ1113]Wall

花式水题,其实就是求个凸包,只不过每个点是圆,其实我们可以求出凸包的周长加上一个圆的周长即可。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>

const int MAXN=1005;
const double PI=std::acos(-1);

struct P
{
    double x,y;
    P(double x=0,double y=0):x(x),y(y){};
};
P operator +(P a,P b){return P(a.x+b.x,a.y+b.y);}
P operator -(P a,P b){return P(a.x-b.x,a.y-b.y);}
double dot(P a,P b){return a.x*b.x+a.y*b.y;}
double cross(P a,P b){return a.x*b.y-a.y*b.x;}
double dis(P a,P b){return std::sqrt(dot(b-a,b-a));}
bool cmp(const P &a,const P &b)
{return a.x<b.x||(a.x==b.x&&a.y<b.y);}

int N,L;
P pt[MAXN],ch[MAXN];int ccnt;
inline void calCH()
{
    int i;
    std::sort(pt+1,pt+N+1,cmp);
    for(i=1;i<=N;i++)
    {
        while(ccnt>1&&cross(ch[ccnt]-ch[ccnt-1],pt[i]-ch[ccnt-1])<=0)
            ccnt--;
        ch[++ccnt]=pt[i];
    }
    int tmp=ccnt;
    for(i=N-1;i;i--)
    {
        while(ccnt>tmp&&cross(ch[ccnt]-ch[ccnt-1],pt[i]-ch[ccnt-1])<=0)
            ccnt--;
        ch[++ccnt]=pt[i];
    }
    if(N>=2) ccnt--;
}

int main()
{
    int i,x,y;
    scanf("%d%d",&N,&L);
    for(i=1;i<=N;i++) scanf("%d%d",&x,&y),pt[i]=P(x,y);
    calCH();
    double ans=0;
    for(i=1;i<=ccnt;i++) ans+=dis(ch[i],ch[i+1]);
    ans+=2*PI*L;
    if(ans-std::floor(ans)<0.5) ans=std::floor(ans);
    else ans=std::ceil(ans);
    printf("%.0f\n",ans);
    return 0;
}

[BZOJ1027][JSOI2007]合金

给出 m 种原材料合金,要铸造 n 种需要的合金,求最少需要多少种原材料。

n,m500

其实这道题跟凸包算法没关系。

个人认为,这类将一类实际问题转化为抽象问题解决的才是值得做的好题批判一下 ZJ 树的统计那种题。本题其实可以用几何方法做,首先,我们确定:由于第三维可以由前两维确定,所以用平面几何方就可以做。

之后,对于两个原料来说,可以合成的合金就在起线段上;进一步推广:多个原料可以合成的合金就在其凸包总点集中,所以问题就转化为了给出 n 个点,求 m 个点中最少要去多少个围成的多边形能圈住这 n 个点。

然后呢,我们又把几何题转化为了图论题:直接用 Floyd 求最小环。

具体做法是:如果目标点都在线段 (u,v) 的一侧,那我们令 dis[u][v]=1 ,否则 dis[u][v]= ,注意初始时 dis[i][i]= ,最后答案就是 Min{dis[i][i]} (由于我们的起点一定要选在环里,而我们不能肯定那个点一定要选,所以要对所有点都跑最小环然后取最小值)

注意要特判所有点都重合或共线的情况。

#include<iostream>
#include<cstdio>
#include<cmath>

const int MAXN=505,INF=1e8;
const double eps=1e-10;

int n,m;

struct P
{
    double x,y;
    P(double x=0,double y=0):x(x),y(y){};
}A[MAXN],B[MAXN];

P operator +(P a,P b){return P(a.x+b.x,a.y+b.y);}
P operator -(P a,P b){return P(a.x-b.x,a.y-b.y);}
double cross(P a,P b){return a.x*b.y-a.y*b.x;}

int dis[MAXN][MAXN];

bool col(P a,P b)
{
    int i;
    if(a.x>b.x) std::swap(a,b);
    for(i=1;i<=m;i++)
        if(B[i].x<a.x||B[i].x>b.x) return false;
    if(a.y>b.y) std::swap(a,b);
    for(i=1;i<=m;i++)
        if(B[i].y<a.y||B[i].y>b.y) return false;
    return true;
}

int jud(P a,P b)
{
    int c1=0,c2=0;
    for(int i=1;i<=m;i++)
    {
        double t=cross(b-a,B[i]-a);
        if(t>eps) c1++;
        if(t<-eps) c2++;
        if(c1*c2) return 0;
    }
    if(!c1&&!c2&&col(a,b)){printf("2\n");return -1;}
    if(c1) return 1;
    if(c2) return 2;
    return 3;
}

void floyd()
{
    int ans=INF;
    int i,j,k;
    for(k=1;k<=n;k++)
        for(i=1;i<=n;i++)
            if(dis[i][k]<INF)
                for(j=1;j<=n;j++)
                    dis[i][j]=std::min(dis[i][j],dis[i][k]+dis[k][j]);
    for(i=1;i<=n;i++) ans=std::min(ans,dis[i][i]);
    if(ans==INF||ans<=2) printf("-1\n");
    else printf("%d",ans);
}

void solve()
{
    int i,j;
    for(i=1;i<=n;i++)
        for(j=i+1;j<=n;j++)
        {
            int flag=jud(A[i],A[j]);
            if(flag==-1) return;
            if(flag==1) dis[i][j]=1;
            else if(flag==2) dis[j][i]=1;
            else if(flag==3) dis[i][j]=dis[j][i]=1;
        }
    floyd();
}

bool spj()
{
    for(int i=1;i<=m;i++)
        if(fabs(B[i].x-A[1].x)>eps||fabs(B[i].y-A[1].y)>eps)
            return 0;
    printf("1\n");
    return 1;
}

int main()
{
    int i,j;
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++) for(j=1;j<=n;j++) dis[i][j]=INF;
    double tmp;
    for(i=1;i<=n;i++) scanf("%lf%lf%lf",&A[i].x,&A[i].y,&tmp);
    for(i=1;i<=m;i++) scanf("%lf%lf%lf",&B[i].x,&B[i].y,&tmp);
    if(spj()) return 0;
    else solve();return 0;
}

[BZOJ4570][SCOI2016]妖怪

n 只妖怪,每只妖怪有 atk,dnf 两种属性,环境对妖怪有影响,在环境 (a,b) 中,一直妖怪可以选择降低自己 ka 点攻击力,提升 kb 点防御力或者,提升自己 ka 点攻击力,降低 kb 点防御力。定义妖怪在某一环境中的战斗力为在此种环境下所能达到的最大 atk 值加上最大 dfn 值。求在最不利的环境下, n 只妖怪中的战斗力最大值。

n106,atk,dfn108

半平面交

旋转卡壳

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值