[POI2008]TROTriangles

X.[POI2008]TRO-Triangles

本题介绍两种做法。

一种是我自己的做法:

考虑某\(\triangle ABC\)\(2S_{\triangle ABC}=\Big|\vec{AB}\times\vec{AC}\Big|=\Big|(\vec{B}-\vec{A})\times(\vec{C}-\vec{A})\Big|\)

于是我们考虑固定某个\(A\),这时就可以令\(\vec{B'}=\vec{B}-\vec{A},\vec{C'}=\vec{C}-\vec{A}\),这样我们只需要求出所有的\(\Big|\vec{B'}\times\vec{C'}\Big|\)即可。

如果它没有前缀和,我们完全可以直接乘法分配律掉;但是套上绝对值,咋办呢?

我们考虑到,设\(\theta\)表示\(\vec{B'}\)\(\vec{C'}\)的夹角(其中\(\vec{B'}\)\(\vec{C'}\)的逆时针方向),则只有在\(\theta\in[0,\pi)\)时,绝对值内的东西才为正;而\(\theta=\theta_{B}-\theta_{C}\),其中\(\theta_B\)\(B\)的辐角。于是我们将所有东西依照辐角排序,并且用一个two-pointers统计对于某个\(B\),所有有\(\theta_C\in[\theta_B,\theta_B+\pi)\)\(C\)之和。然后直接叉积在一起即可。

最终答案要除以\(6\),因为每个三角形都在\(A,B,C\)三个顶点被统计了三次,并且一开始的式子是\(2S_{\triangle ABC}\)

复杂度\(O(n^2\log n)\)。鉴于此题极度卡常,最后挂掉了。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m;
const double pi=acos(-1);
struct Vector{
	int x,y;
	Vector(int X=0,int Y=0){x=X,y=Y;}
	friend Vector operator +(const Vector &u,const Vector &v){return Vector(u.x+v.x,u.y+v.y);}
	void operator +=(const Vector &v){x+=v.x,y+=v.y;}
	friend Vector operator -(const Vector &u,const Vector &v){return Vector(u.x-v.x,u.y-v.y);}
	void operator -=(const Vector &v){x-=v.x,y-=v.y;}
	friend Vector operator *(const Vector &u,const int &v){return Vector(u.x*v,u.y*v);}
	friend Vector operator /(const Vector &u,const int &v){return Vector(u.x/v,u.y/v);}
	friend ll operator &(const Vector &u,const Vector &v){return 1ll*u.x*v.y-1ll*u.y*v.x;}//cross times
	friend ll operator |(const Vector &u,const Vector &v){return 1ll*u.x*v.x+1ll*u.y*v.y;}//point times
	double operator !()const{return atan2(y,x);}//the angle of a vector
	friend bool operator <(const Vector &u,const Vector &v){return !u<!v;}
	void read(){scanf("%d%d",&x,&y);}
	void print(){printf("(%d,%d)",x,y);}
}p[3010],q[3010];
typedef Vector Point;
ll res;
int main(){
	scanf("%d",&n);
	for(int i=0;i<n;i++)p[i].read();
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++)q[j]=p[j]-p[i];
		sort(q,q+n);
		m=1;for(int j=1;j<n;j++)if((q[j]&q[m-1])||(q[j]|q[m-1])<0)q[m++]=q[j];else q[m-1]+=q[j];
//		for(int j=0;j<m;j++)q[j].print();puts("");
		Vector sum;
		int len=0;
		for(int j=0,k=0;j<m;j++){
			if(!len)sum=sum+q[k],len++,k=(k+1)%m;
			for(;len<m&&(q[j]&q[k])>0;k=(k+1)%m)sum+=q[k],len++;
//			printf("%lld\n",q[j]&sum);
			res+=q[j]&sum;
			sum-=q[j],len--;
		}
	}
	res/=3;
	printf("%lld.%d\n",res>>1,(res&1)?5:0);
	return 0;
} 

一种是正解做法。

我们考虑对于每个三角形,我们只在它左下方的节点统计一次。方法很简单,一开始将所有顶点先按照\(x\)为第一关键字,\(y\)为第二关键字排一次序,之后每次只需要统计后缀即可。

然后,每个节点所统计的所有东西,都在其右侧;故只需要进行一遍极角排序,对于一个节点,它后方的所有东西,都有叉积\(>0\)。所有统计后缀和并进行叉积即可。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m;
const double pi=acos(-1);
struct Vector{
	int x,y;
	Vector(int X=0,int Y=0){x=X,y=Y;}
	friend Vector operator +(const Vector &u,const Vector &v){return Vector(u.x+v.x,u.y+v.y);}
	void operator +=(const Vector &v){x+=v.x,y+=v.y;}
	friend Vector operator -(const Vector &u,const Vector &v){return Vector(u.x-v.x,u.y-v.y);}
	void operator -=(const Vector &v){x-=v.x,y-=v.y;}
	friend Vector operator *(const Vector &u,const int &v){return Vector(u.x*v,u.y*v);}
	friend Vector operator /(const Vector &u,const int &v){return Vector(u.x/v,u.y/v);}
	friend ll operator &(const Vector &u,const Vector &v){return 1ll*u.x*v.y-1ll*u.y*v.x;}//cross times
	friend ll operator |(const Vector &u,const Vector &v){return 1ll*u.x*v.x+1ll*u.y*v.y;}//point times
	friend bool operator <(const Vector &u,const Vector &v){return (u&v)>0;}
	void read(){scanf("%d%d",&x,&y);}
	void print(){printf("(%d,%d)",x,y);}
}p[3010],q[3010];
typedef Vector Point;
ll res;
bool cmp(const Vector &u,const Vector &v){return (u.x==v.x)?u.y<v.y:u.x<v.x;}
int main(){
	scanf("%d",&n);
	for(int i=0;i<n;i++)p[i].read();
	sort(p,p+n,cmp);
	for(int i=0;i<n;i++){
		for(int j=i+1;j<n;j++)q[j]=p[j]-p[i];
		sort(q+i+1,q+n);
		for(int k=n-1;k>i;k--)res+=(q[k]&q[k+1]),q[k]+=q[k+1];
	}
	printf("%lld.%d\n",res>>1,(res&1)?5:0);
	return 0;
} 

原文地址:https://www.cnblogs.com/Troverld/p/14619316.html