CF1019D-Large Triangle【计算几何,二分】

正题

题目链接:https://www.luogu.com.cn/problem/CF1019D


题目大意

给出\(n\)个点,保证没有三点共线,求是否有三个点围成的三角形面积恰好为\(S\)

\(3\leq n\leq 2\times 10^3,1\leq S\leq 2\times 10^{18}\)


解题思路

一个暴力的思想是我们可以去枚举底边,然后找顶点。

此时底边的长度固定,而高度就是顶点离底边所在直线的距离,而这题问的是需要找面积为\(S\)的,所以我们可以考虑从近到远二分。

但是如何得到距离直线从近到远的序列,因为我们是枚举的,我们考虑能否从上次的信息得到这次的信息,主要到原来顺序中的一对数\(x,y\)满足\(x\)离的更近当且仅当原来枚举的线段斜率小于\(x\)\(y\)的线段斜率,而同理当枚举到斜率大于它时\(x\)就排到\(y\)后面了。

那么这样方法就已经很显然了,我们把所有点对之间的线段按照斜率从小到大排序枚举,然后处理完\(x\rightarrow y\)后交换\(x,y\)在序列中的顺序就好了,这样每次维护好序列后我们就可以二分做了。

时间复杂度:\(O(n^2\log n^2)\)


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long 
using namespace std;
const ll N=2100;
struct point{
	ll x,y,id;
	point(ll xx=0,ll yy=0)
	{x=xx;y=yy;}
}a[N],e[N*N/2];
ll n,s,cnt,rk[N],sa[N];
point operator-(point a,point b)
{return point(a.x-b.x,a.y-b.y);}
ll operator^(point a,point b)
{return a.x*b.y-a.y*b.x;}
bool cmp(point x,point y)
{return x.x<y.x;}
bool cMp(point x,point y)
{return ((a[x.y]-a[x.x])^(a[y.y]-a[y.x]))>0;}
ll calc(point a,point b,point c)
{return abs((b-a)^(c-a));}
signed main()
{
	freopen("triangle.in","r",stdin);
	freopen("triangle.out","w",stdout);
	scanf("%lld%lld",&n,&s);s=s*2ll;
	for(ll i=1;i<=n;i++)
		scanf("%lld%lld",&a[i].x,&a[i].y);
	sort(a+1,a+1+n,cmp);
	for(ll i=1;i<=n;i++)
		for(ll j=1;j<i;j++)
			e[++cnt]=point(j,i);
	sort(e+1,e+1+cnt,cMp);
	for(ll i=1;i<=n;i++)rk[i]=sa[i]=i;
	for(ll i=1;i<=cnt;i++){
		ll x=e[i].x,y=e[i].y;
		ll l=1,r=min(rk[x],rk[y])-1;
		while(l<=r){
			ll mid=(l+r)>>1;
			if(calc(a[x],a[y],a[sa[mid]])<s)r=mid-1;
			else l=mid+1;
		}
		if(r&&calc(a[x],a[y],a[sa[r]])==s){
			puts("Yes");
			printf("%lld %lld\n",a[x].x,a[x].y);
			printf("%lld %lld\n",a[y].x,a[y].y);
			printf("%lld %lld\n",a[sa[r]].x,a[sa[r]].y);
			return 0;
		}
		swap(rk[x],rk[y]);
		swap(sa[rk[x]],sa[rk[y]]);
	}
	puts("No");
	return 0;
}
posted @ 2022-02-08 20:17  QuantAsk  阅读(41)  评论(0编辑  收藏  举报