【洛谷5286】[HNOI2019] 鱼(计算几何)

点此看题面

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

核心思路

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

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

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

\(BC\)的方案数

预处理

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

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

  • \(\because BC\bot AD,\therefore BC\)\(AD\)的斜率相乘为\(-1\)
  • \(\because AD\)平分\(BC,\therefore AD\)必然经过\(BC\)的中点。

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

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

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

\(\because BC\)中点\(G\)坐标为\((\frac{x_B+x_C}2,\frac{y_B+y_C}2),\therefore 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
}

转载于:https://www.cnblogs.com/chenxiaoran666/p/Luogu5286.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值