【XSY3920】简单的几何题(几何,凸包)
题面
题解
易知 \(v\neq 0\),那么直接考虑条件:
设 \(u'=\dfrac{u}{v}\),\(v'=\dfrac{1}{v}\),\(a'=\dfrac{a}{b}\),\(b'=\dfrac{1}{b}\),则原不等式变为:
考虑配方:
其中左右分别可以看成是 \((u',v')\) 和 \((a',b')\) 到同一个点 \(\left(\dfrac{1}{2x_i},\dfrac{y_i}{2x_i}\right)\) 的距离,其中 \((a',b')\) 到 \(\left(\dfrac{1}{2x_i},\dfrac{y_i}{2x_i}\right)\) 的距离是已知的,记为 \(R_i\)。
那么这个不等式就是要求 \((u',v')\) 这个点在以 \(\left(\dfrac{1}{2x_i},\dfrac{y_i}{2x_i}\right)\) 为圆心、\(R_i\) 为半径的圆内或圆上,记这个圆为圆 \(O_i\)(\(O_i=\left(\dfrac{1}{2x_i},\dfrac{y_i}{2x_i}\right)\)),那么 \(\odot O_i\) 过点 \((a',b')\)。
那么如果 \(i\) 是答案的一种,当且仅当存在点 \((u',v')\) 满足它被 \(\odot O_i\) 包含(“包含” 指含边界),且 \(\forall j\neq i\) 有 \((u',v')\) 在 \(\odot O_j\) 外。也就是存在点 \((u',v')\) 恰好只被圆 \(i\) 包含。
如何找到这些 \(i\) 呢?
我们考虑求出点集 \(\{O,O_1,O_2,\cdots,O_n\}\) 的凸包,那么 \(i\) 是答案的一种当且仅当 \(O_i\) 在凸包上。
感性证明:(以下证明都十分感性,真正的证明应该是用反演变换化曲为直来证)
分情况讨论:
-
若 \(O\) 不在凸包上。
那么考虑一个凸包内(不含边界)的点 \(A\),显然一定可以找到两个在凸包上的相邻点 \(B,C\) 使得 \(A\) 在 \(\triangle OBC\) 内,如图:
我们把这个三角形抽出来单独看,作出 \(\odot A,\odot B,\odot C\):
设 \(\odot A\) 的过点 \(O\) 的直径交 \(\odot A\) 于另一点 \(P_A\),同理设出 \(P_B\) 和 \(P_C\)。
设 \(\odot B\) 与 \(\odot C\) 交于点 \(Q\),那么容易得到 \(\angle P_BQO=\angle P_CQO=90°\),那么 \(P_B,Q,P_C\) 三点共线。
容易看出 \(\triangle P_BOP_C\) 是由 \(\triangle BOC\) 位似变换得到,所以 \(A\) 在 \(\triangle BOC\) 内运动可以看作是 \(P_A\) 在 \(\triangle P_BOP_C\) 内运动:(颜色可能有点变化,请不要在意)
那么容易看出 \(\odot B\) 与 \(\odot C\) 的并集一定包含 \(\odot A\)。
所以 \(\odot A\) 所对应的 \(i\) 肯定不符合要求。
-
若 \(O\) 在凸包上。
与第一种情况是类似的,对于凸包内(不含边界)的某一个点 \(A\),同样一定可以找到两个在凸包上的相邻点 \(B,C\) 使得 \(A\) 在 \(\triangle OBC\) 内,如图:
接下来的证明过程和第一种情况类似。
感性地证毕。
整道题的大致流程就是这样。
还有一个小细节,你发现如果用 double
存点的话精度会爆炸。
然后又发现每一个点都是 \(\left(\dfrac{x}{z},\dfrac{y}{z}\right)\) 的形式,所以我们用三元组 \((x,y,z)\) 存储一个点即可。
然后叉积判断等用 __int128
即可,具体详见代码。
代码如下:(细节很多)
#include<bits/stdc++.h>
#define N 100010
#define ll long long
#define lll __int128
using namespace std;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^'0');
ch=getchar();
}
return x*f;
}
struct Point
{
ll x,y,z;
int id;
}p[N],st[N];
inline int check(Point a,Point b,Point c)
{
lll x=(lll)(a.x*b.z-b.x*a.z)*(lll)(a.y*c.z-a.z*c.y);
lll y=(lll)(a.x*c.z-c.x*a.z)*(lll)(a.y*b.z-a.z*b.y);
if(x==y) return 0;
return x<y?-1:1;
}
inline bool inside(Point a,Point b,Point c)
{
if(a.x*b.z==b.x*a.z||b.x*c.z==c.x*b.z) return 1;
return !((a.x*b.z<b.x*a.z)^(b.x*c.z<c.x*b.z));
}
inline bool operator < (Point a,Point b)
{
int tmp=check(p[1],a,b);
if(!tmp)
{
if(p[1].x*a.z<=a.x*p[1].z&&p[1].x*b.z<=b.x*p[1].z) return a.x*b.z<b.x*a.z;
else return a.x*b.z>b.x*a.z;
}
return tmp>0;
}
inline bool operator == (Point a,Point b)
{
return a.x*b.z==b.x*a.z&&a.y*b.z==b.y*a.z;
}
int n;
int top;
bool vis[N];
bool ban[N];
void Graham()
{
for(int i=2;i<=n;i++)
if(p[i].y*p[1].z<p[1].y*p[i].z||
(p[i].y*p[1].z==p[1].y*p[i].z&&p[i].x*p[1].z<p[1].x*p[i].z))
swap(p[1],p[i]);
sort(p+2,p+n+1);
st[++top]=p[1];
for(register int i=2;i<=n;i++)
{
while(top>1)
{
int tmp=check(st[top-1],st[top],p[i]);
if(!tmp&&st[top]==p[i]) ban[st[top].id]=ban[p[i].id]=1;
if(tmp<0||(!tmp&&inside(st[top-1],st[top],p[i]))) top--;
else break;
}
st[++top]=p[i];
}
for(register int i=1;i<=top;i++) vis[st[i].id]=1;
}
int main()
{
n=read()+1;
p[1].x=read(),p[1].y=1,p[1].z=read();
for(register int i=2;i<=n;i++)
p[i].x=1,p[i].z=2ll*read(),p[i].y=read(),p[i].id=i-1;
Graham();
for(register int i=1;i<n;i++)
if(vis[i]&&!ban[i]) printf("%d ",i);
return 0;
}
/*
1 2 3
4 5
*/
/*
2 2 3
4 5
6 7
*/
/*
2 1 666666666
333333333 1
455943374 506861437
*/
/*
2 236701642 822463349
213494995 807598793
728662831 347575608
*/