计算几何
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;
}