点此看题面

大致题意: 给你\(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
}

【洛谷5286】[HNOI2019] 鱼(计算几何)的更多相关文章

  1. 洛谷P5292 [HNOI2019]校园旅行(二分图+最短路)

    题面 传送门 题解 如果暴力的话,我们可以把所有的二元组全都扔进一个队列里,然后每次往两边更新同色点,这样的话复杂度是\(O(m^2)\) 怎么优化呢? 对于一个同色联通块,如果它是一个二分图,我们只 ...

  2. 洛谷CF1071E Rain Protection(计算几何,闵可夫斯基和,凸包,二分答案)

    洛谷题目传送门 CF题目传送门 对于这题,我无力吐槽. 虽然式子还是不难想,做法也随便口胡,但是一些鬼畜边界情况就是判不对. 首先显然二分答案. 对于每一个雨滴,它出现的时刻我们的绳子必须落在它上面. ...

  3. 洛谷 P1736 创意吃鱼法

    题目描述 题目链接:https://www.luogu.org/problemnew/show/P1736 回到家中的猫猫把三桶鱼全部转移到了她那长方形大池子中,然后开始思考:到底要以何种方法吃鱼呢( ...

  4. 洛谷P3222 [HNOI2012]射箭(计算几何,半平面交,双端队列)

    洛谷题目传送门 设抛物线方程为\(y=ax^2+bx(a<0,b>0)\),我们想要求出一组\(a,b\)使得它尽可能满足更多的要求.这个显然可以二分答案. 如何check当前的\(mid ...

  5. 洛谷 P2701 [USACO5.3]巨大的牛棚Big Barn Label:二维数组前缀和 你够了 这次我用DP

    题目背景 (USACO 5.3.4) 题目描述 农夫约翰想要在他的正方形农场上建造一座正方形大牛棚.他讨厌在他的农场中砍树,想找一个能够让他在空旷无树的地方修建牛棚的地方.我们假定,他的农场划分成 N ...

  6. 洛谷:P3281 [SCOI2013]数数 (优秀的解法)

    刷了这么久的数位 dp ,照样被这题虐,还从早上虐到晚上,对自己无语...(机房里又是只有我一个人,寂寞.) 题目:洛谷P3281 [SCOI2013]数数 题目描述 Fish 是一条生活在海里的鱼, ...

  7. 洛谷P1387 最大正方形

    题目描述 题目链接:https://www.luogu.org/problemnew/show/P1387 在一个n*m的只包含0和1的矩阵里找出一个不包含0的最大正方形,输出边长. 输入输出格式 输 ...

  8. 洛谷 AT1350 深さ優先探索

    洛谷 AT1350 深さ優先探索 洛谷传送门 题意翻译 高桥先生住的小区是长方形的,被划分成一个个格子.高桥先生想从家里去鱼店,高桥先生每次可以走到他前后左右四个格子中的其中一个,但不能斜着走,也不能 ...

  9. Java实现洛谷 P1428 小鱼比可爱

    题目描述 人比人,气死人:鱼比鱼,难死鱼.小鱼最近参加了一个"比可爱"比赛,比的是每只鱼的可爱程度.参赛的鱼被从左到右排成一排,头都朝向左边,然后每只鱼会得到一个整数数值,表示这只 ...

随机推荐

  1. PIE SDK图像裁剪

    1.算法功能简介 图像裁剪的目的是获取选定的影像范围区域.图像裁切工具提供像素范围裁切.矢量裁切.栅格图像裁切和几何图元裁切四种方式. 像素范围裁切是基于像素坐标获取矩形裁切区域的裁切方式:矢量裁切是 ...

  2. win10 装centos7 虚拟机

    1.下载VMware Workstation 64版本 https://www.vmware.com/products/workstation-pro/workstation-pro-evaluati ...

  3. spring利用cors处理跨域问题

    参考 http://blog.csdn.net/isea533/article/details/50449907 写的很全面 http://blog.csdn.net/a317560315/artic ...

  4. Percona Mysql备份

    介绍 Percona是唯一一款开源.免费的mysql热备份工具,实现了对InnoDB数据库的非阻塞式的备份.有如下优势:1.完整.快速.可靠的备份2.备份期间不打断事务执行(innodb引擎)3.备份 ...

  5. Linux——【rpm、yun、源码包】安装

    RPM包或者安装源码包 在windows下安装一个软件很轻松,只要双击.exe的文件,安装提示连续“下一步”即可,然而linux系统下安装一个软件似乎并不那么轻松,因为我们不是在图形界面下.所以我们要 ...

  6. oracle 基础知识(十一)----表空间结构

    一,逻辑结构图 二.tablespace 01,Oracle表空间 它是一个逻辑的概念,它在物理上是不存在的. 02,oracle 存储结构 03.表空间特性 一个数据库可以包含多个表空间,一个表空间 ...

  7. 04-struts2获得参数

    1 struts2 获得参数 1-属性驱动获得参数 1 Demo8Action package www.test.c_param; import java.util.Date; import com. ...

  8. poi 多行合并

    poi做多行合并,一定需要先绘制单元格,然后写入数据,最后合并,不然各种坑啊. 合并单元格所使用的方法: sheet.addMergedRegion( CellRangeAddress  cellRa ...

  9. pat04-树4. Root of AVL Tree (25)

    04-树4. Root of AVL Tree (25) 时间限制 100 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 CHEN, Yue An A ...

  10. [转]微信小程序,开发大起底

    本文转自:http://blog.csdn.net/baiyuzhong2012/article/details/54378497 作者简介:张智超,北京微函工坊开发工程师,CSDN微信开发知识库特邀 ...