大致题意: 给你(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)斜率为:
又(ecause BC)中点(G)坐标为((frac{x_B+x_C}2,frac{y_B+y_C}2), herefore AD)截距为:
这样其实我们已经求出了(AD)的斜率和截距,但如果你像我一样无聊,可以看一下我对于这里截距的进一步化简。
这个截距的式子显得过于冗长,因此我们可以再转化一下得到:
其中,(B-C,B+C)皆为向量。
然后,了解一些计算几何公式的人就可以发现,(x_{B-C}*x_{B+C}+y_{B-C}*y_{B+C})其实是一个点积的形式!
于是我们最后得到一个简单的式子:
而求出了斜率和截距之后,我们就相当于求出了(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
}