计算几何学习笔记

说是计算几何学习笔记.其实只是写了极角排序和凸包.因为剩下的我都不会

目录:

  1. 极角排序
  2. 凸包
  3. 圆的反演\最小圆覆盖

参考自\(chanle\)_\(cyx\)大神的\(blog\)一类极角排序题
以讲例题为主.

  1. 极角排序
  • 顾名思义,即以某个点为原点,以其他点到这个点的夹角为极角,进行排序.
  • 意义:可以得到一定程度上的单调性,方便解决一些问题
  • 参考代码
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)\)
  • 估计很难写,有一些细节不是很懂怎么处理,如求某直线的垂直平分线,两点求直线(其实这些都简单,主要是存在斜率方程不合法的情况大概要很多特判).
posted @ 2021-03-13 16:30  y_dove  阅读(149)  评论(2编辑  收藏  举报