计算几何学习笔记
说是计算几何学习笔记.其实只是写了极角排序和凸包.因为剩下的我都不会
目录:
- 极角排序
- 凸包
- 圆的反演\最小圆覆盖
参考自\(chanle\)_\(cyx\)大神的\(blog\)一类极角排序题
以讲例题为主.
- 极角排序
- 顾名思义,即以某个点为原点,以其他点到这个点的夹角为极角,进行排序.
- 意义:可以得到一定程度上的单调性,方便解决一些问题
- 参考代码
bool cmp(poi a,poi b){//s是原点
vec p = a - s,q = b - s;
if(p.x == 0 && q.x == 0) return p.y < q.y;
int mi = min(p.x,q.x),mx = max(p.x,q.x);
if(mi < 0 && mx >= 0) return p.x < q.x;
ll tmp = p ^ q;
if(tmp == 0) return p.len() < q.len();
return tmp > 0;
}
- 首先我们注意到,直接按叉积排序(如果按角度是可以的,但\(atan\)精度和时间效率都不如叉积)是没办法满足完全偏序关系的,只能对每个半平面满足.所以有以上的一堆特判.
- 例题1:USACO10OPENTriangle Counting G
- 题意简述:平面上给\(n\)个点(保证不存在\((0,0)\)),定义黄金三角形为包含\((0,0)\)的三角形,求\(n\)个点能构成的黄金三角形个数.
-
solution:
- 考虑直接计算包含的个数不好算,我们考虑容斥一下,计算不包含原点的个数.考虑进行极角排序,考虑某一个点\(P\),若以其为极角最小的点,其能构成的不包含原点的三角形有哪些.设极角为\(\alpha\),则极角在\([\alpha,\alpha + \pi)\)的点和它构成的三角形不包含原点,设这些点有\(k\)个,则贡献\(\binom{k}{2}\)个.双指针扫一遍即可
- 时间复杂度\(O(n\log n)\)(瓶颈在排序).
- 代码如下:
/*[USACO10OPEN]Triangle Counting G*/
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define int long long
ll read(){
char c = getchar();
ll x = 0;int f = 1;
while(c < '0' || c > '9') f = (c == '-')?-1:f,c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48,c = getchar();
return x * f;
}
const int N = 2e5 + 10;
struct poi{
ll x,y;
void in(){
x = read(),y = read();
}
poi (ll _x = 0,ll _y = 0){
x = _x,y = _y;
}
poi operator -(const poi a){
return poi(x - a.x,y - a.y);
}
poi operator +(const poi a){
return poi(x + a.x,y + a.y);
}
ll operator ^(const poi a){/*叉乘*/
return x*a.y-y*a.x;
}
ll len(){
return x*x+y*y;
}
}s,ver[N];
typedef poi vec;
bool cmp(poi a,poi b){
vec p = a - s,q = b - s;
if(p.x == 0 && q.x == 0) return p.y < q.y;
int mi = min(p.x,q.x),mx = max(p.x,q.x);
if(mi < 0 && mx >= 0) return p.x < q.x;
ll tmp = p ^ q;
if(tmp == 0) return p.len() < q.len();
return tmp > 0;
}
int n;
signed main(){
n = read();
if(n < 3){
puts("0");
return 0;
}
for(int i = 1; i <= n; ++i){
ver[i].in();
if(ver[i].y < ver[1].y || (ver[i].y == ver[1].y && ver[i].x < ver[1].x)){
swap(ver[i],ver[1]);
}
}
ll ans = 1ll * n * (n - 1) * (n - 2) / 6;
s = poi(0,0);
sort(ver+1,ver+n+1,cmp);
for(int i = 1; i <= n; ++i) ver[i+n] = ver[i];
int r = 2;
for(int l = 1; l <= n; ++l){
vec A = poi(0,0) - ver[l];
while(r <= 2 * n && ((ver[r] - ver[l]) ^ A) >= 0){
r++;
}
r--;
if(l < r) ans -= (1ll * (r - l) * (r - l - 1) / 2);
r++;
}
printf("%lld\n",ans);
return 0;
}
例题2.CF1284E New Year and Castle Construction
- 题意简述:给一个大小为\(n\)的点集\(S\).定义\(f(p)\)覆盖点\(p\)的不同四边形(四边形的四个顶点都在点集内,且\(p\)不能是顶点)的个数.求\(\sum_{p \in S}f(p)\).(保证任三点不共线)
-
\(n \leq 2500\)
- 首先如果我们枚举每个点\(p\)算出\(f(p)\),那么可以直接套用上面的做法.复杂度\(O(n^2logn)\).
- 但是官方题解给出一种更加巧妙的做法,我们考虑任意五个点对答案构成的贡献.考虑这五个点的凸包大小,设\(x_i\)表示5个点构成的大小为\(i\)的凸包有几个.
- 若为\(3\),则贡献\(2\)
- 若为\(4\),则贡献\(1\)
- 若为\(5\),则不贡献.
- 所以\(ans = 2 * x_3 + x_4\)
- 考虑\(\binom{n}{5} = x_3 + x_4 + x_5\).
- 令\(X = 3 * x_3 + 4 * x_4 + 5 * x_5\)
- 所以\(ans = 5 * \binom{n}{5} - X\)
- 考虑\(X\)的组合意义,发现恰好等价于凸包的边数.那么我们枚举每一条边.考虑其作为多少个凸包即可.发现只要剩下三个点都在这条边的隔开的某个半平面内.这条边即可作为凸包的一条边.
- 依然枚举每个点\(p\),以其为原点.极角排序双指针扫一遍即可.
- 评测链接:code
例题3:HNOI2019鱼
题意简述:简述不来,看题目吧
solution
- 玩了半个晚上终于想出来(然而不会写,要是考试考到就爆零了).
- 大概先口胡一下做法吧.考虑枚举\(A,D\)两点,那么鱼身和鱼尾是独立的,可以分开来做.
- 考虑鱼尾怎么做.考虑固定\(D\)点,对\(A\)极角排序,那么能选的点是一个旋转的半平面(具体来说和线段\(AD\)垂直).\(hash_s\)存到\(D\)的距离为\(s\)的点,双指针扫一下即可.\(O(n^2)\)
- 考虑鱼身怎么做,考虑\(AD\)很难确定\(EF\),我们反过来想,考虑\(EF\),那么\(AD\)一定在\(EF\)的垂直平分线上,那么预处理\(EF\)的垂直平分线,用\(map\)存进以垂直平分线为下标的数组,按中点\(x\)排序,每次枚举\(AD\)时,算出\(AD\)的直线,访问这个数组,然后二分出合法的\(x\)坐标区间即是合法的答案.时间复杂度\(O(n\log n)\)
- 估计很难写,有一些细节不是很懂怎么处理,如求某直线的垂直平分线,两点求直线(其实这些都简单,主要是存在斜率方程不合法的情况大概要很多特判).