1. 程式人生 > 實用技巧 >LOJ 2882. 「JOISC 2014 Day4」兩個人的星座

LOJ 2882. 「JOISC 2014 Day4」兩個人的星座

LOJ 2882. 「JOISC 2014 Day4」兩個人的星座

對於任意兩個凸多邊形相離,一定可以找到一條直線將它們分在平面的兩個區域

而對於三角形的情況更為特殊

分析可以發現,很難直接列舉三角形外直線計算,而對於任意的兩個合法的三角形,在其6點中較近的4個點中

一定可以從兩個三角形中各選一個點,連出兩條交錯的合法的分界線,例如下圖

那麼可以考慮列舉這樣的一條直線,即確定了兩個分界線上的端點,然後從兩個半平面內選出不同顏色的點

直接列舉,然後\(O(n)\)數出這樣的點,複雜度為\(O(n^3)\)

顯然可以想到列舉一個頂點,然後對於其他極角排序,旋轉另一個點,同步統計半平面內的點個數,複雜度為\(O(n^2\log n)\)

實現上,可以列舉一個點,尺取一個半平面內的點

#include<bits/stdc++.h>
using namespace std;
using ll=int64_t;
enum{N=3010};
int n,X[N],Y[N],I[N],C[N],c,s[2][3],i;
double T[N];
ll ans;
ll E(int j,int k){ return 1ll*(X[j]-X[i])*(Y[k]-Y[i])-1ll*(Y[j]-Y[i])*(X[k]-X[i]); }

int main(){
	for(int i=scanf("%d",&n);i<=n;++i) scanf("%d%d%d",X+i,Y+i,C+i);
	for(i=1;i<=n;++i) {
		c=0;
		memset(s,0,sizeof s);
		for(int j=1;j<=n;++j) if(i!=j) I[++c]=j,T[j]=atan2(Y[j]-Y[i],X[j]-X[i]),s[0][C[j]]++;
		sort(I+1,I+n,[&](int x,int y){ return T[x]<T[y]; });
		int p=1;
		for(int j=1;j<n;++j) {
			while(E(I[j],I[p])>=0) {
				s[0][C[I[p]]]--,s[1][C[I[p]]]++;
				p=p%c+1;
				if(p==j) break;
			}
			ans+=1ll*s[0][(C[i]+1)%3]*s[0][(C[i]+2)%3]*s[1][(C[I[j]]+1)%3]*s[1][(C[I[j]]+2)%3];
			s[1][C[I[j]]]--,s[0][C[I[j]]]++;
		}
	}
	cout<<ans/2;
}

一個更好的寫法是把在\(y\)軸以下的點中心對稱上來,統計時每跨過一個點改變一次

#include<bits/stdc++.h>
using namespace std;
enum{N=3010};
int n,X[N],Y[N],C[N],c,s[2][3],i,j,d,a,b,x,y;
int64_t ans;
struct Node{
	int x,y,d,c;
	Node(){}
	Node(int p,int q,int r) {
		x=p,y=q,c=r,d=0;
		if(y<0||(x<0&&y==0)) d=1,x=-x,y=-y; 
		s[d][c]++;
	}
} A[N];
struct cmp{ int operator () (const Node &a,const Node &b){ return 1ll*a.x*b.y<1ll*a.y*b.x; }};
int main(){
	for(i=scanf("%d",&n);i<=n;++i) scanf("%d%d%d",X+i,Y+i,C+i);
	for(i=1;i<=n;++i) {
		memset(s,c=0,sizeof s),a=(C[i]+1)%3,b=(a+1)%3;
		for(j=1;j<=n;++j) if(i!=j) A[++c]=Node(X[j]-X[i],Y[j]-Y[i],C[j]);
		for(sort(A+1,A+n,cmp()),j=1;j<n;++j) {
			s[A[j].d][c=A[j].c]--,x=(c+1)%3,y=(x+1)%3;
			for(d=0;d<2;++d) ans+=1ll*s[d][a]*s[d][b]*s[!d][x]*s[!d][y];
			s[!A[j].d][c]++;
		}
	}
	cout<<ans/4;
}