zoukankan      html  css  js  c++  java
  • 【洛谷5286】[HNOI2019] 鱼(计算几何)

    点此看题面

    大致题意: 给你(n)个点,让你求鱼形图的数量。

    核心思路

    首先,考虑到(n)这么小,我们可以枚举线段(AD),再去找符合条件的(BC,EF)

    然后,不难发现(BC)(EF)互不影响,因此我们可以分开求对于已知(AD)(BC)(EF)的方案数,然后将其相乘。

    那么我们现在的问题就在于,如何求出(BC)(EF)的方案数了。

    (BC)的方案数

    预处理

    考虑到(AB=AC,BD=CD),用我这点可怜的初中数学知识,都能证明出(AD)垂直平分(BC)(貌似作业里还做过这种题目)。

    则,我们可以换一个角度,如果枚举(BC),那么符合条件的(AD)必然满足两个条件:

    • (ecause BCot AD, herefore BC)(AD)的斜率相乘为(-1)
    • (ecause AD)平分(BC, herefore AD)必然经过(BC)的中点。

    而已知斜率和直线上一点,我们就可以推出这条直线的解析式啦!

    (B(x_B,y_B),C(x_C,y_C)),则(BC)斜率为(frac{y_C-y_B}{x_C-x_B}),( herefore AD)斜率为:

    [-1÷frac{y_C-y_B}{x_C-x_B}=frac{x_B-x_C}{y_C-y_B} ]

    (ecause BC)中点(G)坐标为((frac{x_B+x_C}2,frac{y_B+y_C}2), herefore AD)截距为:

    [frac{x_B-x_C}{y_C-y_B}*frac{x_B+x_C}2-frac{y_B+y_C}2=frac{(x_B-x_C)(x_B+x_C)-(y_C-y_B)(y_B+y_C)}{2(y_C-y_B)} ]

    这样其实我们已经求出了(AD)的斜率和截距,但如果你像我一样无聊,可以看一下我对于这里截距的进一步化简。


    这个截距的式子显得过于冗长,因此我们可以再转化一下得到:

    [frac{(x_B-x_C)(x_B+x_C)-(y_C-y_B)(y_B+y_C)}{2(y_C-y_B)}=frac{(x_B-x_C)(x_B+x_C)+(y_B-y_C)(y_B+y_C)}{2(y_C-y_B)}=frac {x_{B-C}*x_{B+C}+y_{B-C}*y_{B+C}}{2(y_C-y_B)} ]

    其中,(B-C,B+C)皆为向量。

    然后,了解一些计算几何公式的人就可以发现,(x_{B-C}*x_{B+C}+y_{B-C}*y_{B+C})其实是一个点积的形式!

    于是我们最后得到一个简单的式子:

    [frac{(B-C)cdot(B+C)}{2(y_C-y_B)} ]


    而求出了斜率和截距之后,我们就相当于求出了(AD)的函数表达式。

    则可以考虑把(AD)函数表达式相同的(BC)的信息全部扔入同一个(vector)存储下来,方便后面求答案。

    还有要注意的就是对于(B,C)两点(y)相等的情况要特判,因为此时(AD)就不是一次函数了。

    求答案

    枚举(AD)时,我们首先就是求出(AD)的函数表达式,然后到对应(vector)里去求答案。

    此时,我们主要是要满足题目中给出的“(∠BAD,∠BDA)(∠CAD,∠CDA)都是锐角”的要求。

    也就是说,线段(AD)需要与线段(BC)有交点。

    我们先前已经提过要开(vector),但其实不需要直接存下(BC),因为这样不太方便。

    实际上,我们只要存下中点某一坐标的两倍(两倍是为了可以直接用(int)存储),就可以直接在(vector)中通过(upper\_bound)的方式,分别用(A,D)两点的相应坐标查找出两个范围,然后利用前缀和思想来用大的减小的即可得出答案。

    至于应该选择哪一坐标,需要根据具体函数解析式来分析了。

    如形如(x=a)的表达式必须选纵坐标,形如(y=a)的表达式必须选横坐标(因为固定的无法判),否则任选。

    不过反正我们要特判(B,C)纵坐标相等,即(A,D)横坐标相等的情况,则我们干脆对于横坐标相等的取纵坐标,不相等的取横坐标,也方便我们判断应用哪个坐标去(upper\_bound)

    这一过程有一定细节,具体实现详见代码。

    (EF)的方案数

    对于这个,我们可以考虑,过点(D)(AD)的垂线(l),则我们将整个平面划分成了两个半平面,而根据题目要求,点(E,F)必须在点(A)所不在的那个半平面内。

    则如果我们枚举点(D),然后将其余点按极角排序,我们就可以用双指针,来维护对于每个点(A)可能作为点(E,F)的所有点。

    那么什么情况下两个点才能作为一对点(E,F)呢?

    正如题目中说的,需要(DE=DF)

    因此我们开个(map),维护每种到点(D)的距离出现的次数,然后就可以用类似于莫队的方式进行维护了,这应该还是比较简单的。

    注意,这里我对于相同的两个点只统计了一次,因为最后我将答案乘上了(4),即(BC,EF)互换的情况。

    这一过程同样有一定细节,具体实现详见代码。

    代码

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define RL Reg LL
    #define Con const
    #define CI Con int&
    #define CL Con LL&
    #define I inline
    #define W while
    #define N 1000
    #define LL long long
    #define DB long double
    #define eps 1e-10
    #define swap(x,y) (x^=y^=x^=y)
    using namespace std;
    int n,val[N+5][N+5];Con DB Pi=acos(-1); 
    Tp I Ty gcd(Con Ty& x,Con Ty& y) {return y?gcd(y,x%y):x;}
    struct Fr//存储一个分数
    {
    	LL x,y;I Fr(CL a=0,CL b=1) {RL g=gcd(a,b);x=a/g,(y=b/g)<0&&(x=-x,y=-y);}//注意约分
    	I bool operator != (Con Fr& o) Con {return x^o.x||y^o.y;}//不等于
    	I bool operator < (Con Fr& o) Con {return x^o.x?x<o.x:y<o.y;}//这个小于不是真的小于,只是用于区分不同分数来存储到map中
    };
    struct Line//存储一条直线,只存斜率和截距
    {
    	Fr Slope,Incre;I Line(Con Fr& s=Fr(),Con Fr& i=Fr()):Slope(s),Incre(i){}
    	I bool operator < (Con Line& o) Con {return Slope!=o.Slope?Slope<o.Slope:Incre<o.Incre;}//用于区分
    };
    struct Point//存储一个点
    {
    	#define Dot(A,B) (1LL*(A).x*(B).x+1LL*(A).y*(B).y)//点积
    	#define Cro(A,B) (1LL*(A).x*(B).y-1LL*(A).y*(B).x)//叉积
    	#define Len(A) Dot(A,A)//求出长度的平方,之所以不开放是为防炸精度,而且不影响比大小
    	int x,y;I Point(CI a=0,CI b=0):x(a),y(b){}
    	I Point operator + (Con Point& o) Con {return Point(x+o.x,y+o.y);}//点相加
    	I Point operator - (Con Point& o) Con {return Point(x-o.x,y-o.y);}//点相减
    }p[N+5];
    struct Data//存储极角和对应点编号用于排序
    {
    	int pos;DB ang;I Data(CI p=0,Con DB& a=0):pos(p),ang(a){}
    	I bool operator < (Con Data& o) Con {return ang<o.ang;}
    }s[N<<1];
    map<Line,int> pos;vector<int> v[N*N+5];map<LL,int> cnt;
    int main()
    {
    	RI i,j,x,y,z,tot,Pc=0,H,T;RL ans=0;Line w;Point t;
    	for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d%d",&x,&y),p[i]=Point(x,y);
    	for(i=1;i<=n;++i) for(j=i+1;j<=n;++j)
    	{
    		if(p[i].y==p[j].y)//特判y相等
    		{
    			w=Line(Fr(1,0),Fr(p[i].x+p[j].x,2)),//求出截距
    			v[pos.count(w)?pos[w]:pos[w]=++Pc].push_back(p[i].y<<1);//存储中点纵坐标两倍
    			continue;
    		}
    		t=p[j]-p[i],w=Line(Fr(-t.x,t.y),Fr(Dot(t,p[i]+p[j]),2LL*t.y)),//求出斜率和截距
    		v[pos.count(w)?pos[w]:pos[w]=++Pc].push_back(p[i].x+p[j].x);//存储中点横坐标两倍
    	}
    	for(i=1;i<=Pc;++i) sort(v[i].begin(),v[i].end());//排序,用于之后的upper_bound
    	for(i=1;i<=n;++i)//枚举点D
    	{
    		for(tot=0,j=1;j<=n;++j) i^j&&(s[++tot]=Data(j,atan2(p[j].y-p[i].y,p[j].x-p[i].x)),0);//存下极角
    		for(sort(s+1,s+n),j=1;j^n;++j) (s[j+n-1]=s[j]).ang+=2*Pi;//排序,然后将每个点复制一份,方便后面的双指针
    		for(cnt.clear(),tot=H=T=0,j=1;j^n;++j)//双指针
    		{
    			W(s[T+1].ang<s[j].ang+1.5*Pi-eps) ++T,tot+=cnt[Len(p[s[T].pos]-p[i])]++;//加入新进入半平面的点
    			W(s[H+1].ang<s[j].ang+0.5*Pi+eps) ++H,tot-=--cnt[Len(p[s[H].pos]-p[i])];//删除新离开半平面的点
    			val[s[j].pos][i]=tot;//记下结果
    		}
    	}
    	for(i=1;i<=n;++i) for(j=i+1;j<=n;++j)//枚举AD
    	{
    		p[i].x^p[j].x?(x=p[i].x,y=p[j].x):(x=p[i].y,y=p[j].y),x>y&&swap(x,y),t=p[i]-p[j],//求出当前情况下对应哪种坐标
    		w=(p[i].x^p[j].x?Line(Fr(t.y,t.x),Fr(Cro(t,p[i]),t.x)):Line(Fr(1,0),Fr(p[i].x,1))),//求出解析式
    		#define UB(x) upper_bound(v[z].begin(),v[z].end(),x)
    		pos.count(w)&&(z=pos[w],ans+=(UB((y<<1)-1)-UB(x<<1))*(val[i][j]+val[j][i]));//如果该解析式存在,更新答案
    	}return printf("%lld",ans<<2),0;//注意将答案乘4
    }
    
  • 相关阅读:
    1104 Sum of Number Segments (20 分)(数学问题)
    1092 To Buy or Not to Buy (20 分)(hash散列)
    1082 Read Number in Chinese (25 分)(字符串处理)【背】
    1105 Spiral Matrix (25 分)(模拟)
    初识网络安全及搭建网站(内网)
    HTML5开发者需要了解的技巧和工具汇总(转)
    native+web开发模式之web前端经验分享
    移动平台3G手机网站前端开发布局技巧汇总(转)
    Asp.net 中图片存储数据库以及页面读取显示通用方法详解附源码下载
    使用H3Viewer来查看VS2010的帮助文档
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu5286.html
Copyright © 2011-2022 走看看