「JOISC 2014 Day4」两个人的星座 题解

计算几何

Statement

#2882. 「JOISC 2014 Day4」两个人的星座 - 题目 - LibreOJ (loj.ac)

Solution

有性质:如果两个三角形相交或者内含,则一定不存在内公切线(即两个三角形在这条线两侧,并且线上分别有两个三角形的一个顶点)

由于题目保证了不存在三点共线,所以两个三角形相离有这样一个神仙性质:

如果两个三角形相离,那么它们有两条内公切线

所以我们得到一个暴力的算法: \(O(n^2)\)​ 枚举两点得到公切线,\(O(n)\)​ 暴力扫描一下各个点在线的上/下方并统计一下在上/下方各种颜色的点的数量,即记 \(c[1/0][y]\)​ 表示在此公切线上/下方颜色 \(y\) 的数量

容易得到此公切线的答案,这样总共是 \(O(n^3)\) 的,考虑优化

发现每次在公切线更改的时候,只有两条公切线之间的点会变化状态(eg.从线下方到上方)

所以我们考虑枚举公切线的一个端点,然后对其他点进行极角排序,这样每次只有一个点的状态改变,\(O(n^2)\)​ 即可(代码更好懂)

注意这样每对三角形同一条公切线会枚举两次,而相离的三角形有两条公切线,所以最后答案要除 \(\rm 4\)

Code

#include<bits/stdc++.h>
#define ldb long double
#define int long long
using namespace std;
const int N = 3005;
const ldb Pi = acos(-1.0);

struct Vector{
    ldb x,y; int c;
    Vector(ldb x=0,ldb y=0):x(x),y(y){}
    void scan(){scanf("%Lf %Lf %lld",&x,&y,&c);}
    void print(){printf("%.4Lf %.4Lf",x,y);}
    ldb alpha(){return atan2(y,x);}
}a[N];
struct Point{
    Vector p; ldb k;
    Point(){}
    Point(Vector p,ldb k=0):p(p),k(k){}
    bool operator<(const Point&rhs)const{
        return k<rhs.k;
    }
}p[N];
Vector operator+(Vector a,Vector b){return Vector(a.x+b.x,a.y+b.y);}
Vector operator-(Vector a,Vector b){return Vector(a.x-b.x,a.y-b.y);}
int c[2][5],bl[N];
int n,ans;

ldb mabs(ldb a){return a<=0?a+Pi:a;}
int calc(int op,int k){
    if(k==0)return c[op][1]*c[op][2];
    if(k==1)return c[op][0]*c[op][2];
    if(k==2)return c[op][0]*c[op][1];
}

void solve(int id){
    int cnt=0;
    for(int i=1;i<=n;++i)if(i^id)
        p[++cnt]=Point(a[i],mabs((a[i]-a[id]).alpha()));
    sort(p+1,p+1+cnt),memset(c,0,sizeof(c));
    for(int i=1;i<=cnt;++i)
        if(p[i].p.y<a[id].y||p[i].p.y==a[id].y&&p[i].p.x>a[id].x)
            c[0][p[i].p.c]++,bl[i]=0;
        else c[1][p[i].p.c]++,bl[i]=1;
    for(int i=1;i<=cnt;++i){
        c[bl[i]][p[i].p.c]--,bl[i]^=1,
        ans+=calc(0,a[id].c)*calc(1,p[i].p.c)+calc(1,a[id].c)*calc(0,p[i].p.c);
        c[bl[i]][p[i].p.c]++;
    }
}

signed main(){
    scanf("%lld",&n);
    for(int i=1;i<=n;++i)a[i].scan();
    for(int i=1;i<=n;++i)solve(i);
    printf("%lld\n",ans/4);
    return 0;
}
posted @ 2022-02-01 11:28  _Famiglistimo  阅读(110)  评论(0编辑  收藏  举报