近期刷题总结----计算几何

目录

[WOJ2535] 数三角形

[POJ3304] Segments

[codeVS1302] 小矮人

[ZJOI2008]瞭望塔 

[SCOI2007]最大土地面积 

[HAOI2008]下落的圆盘

[BZOJ1249] 动态凸包 

[WOJ4629] Oil 

[HDU4773] Problem of Apollonius 

[BZOJ2961] 共点圆 


[WOJ2535] 数三角形

题意:求包涵原点的三角形个数

解:考虑全部的减去不合法的

不合法的个数可以通过枚举一个点,连接它和原点,如果两个点在同一侧则不合法

发现如果我们只考虑左半边的点,每个点算一次就可以不重不漏

如何维护一个点与原点连线左半边的点?我们可以按与原点的极角排序然后边转边维护


[POJ3304] Segments

题意:问能不能找到一条直线使得所有线段到这根直线的投影至少有一个交点

解:我们发现,过这个交点做一条垂线,那么它经过所有线段

问题转换为能不能找到一根直线穿过所有线段

我们假设找到一根,那么可以通过旋转转到两根线段的两个端点

于是我们暴力枚举两根线段,过它们的4个端点两两做直线,判断穿过所有直线没有即可,判穿过可以用叉积


[codeVS1302] 小矮人

题意:给出 n 个点,m 条直线,判断 n 个点是否在直线同侧

解:如果穿过一定穿过凸包,于是先把 n 个点的凸包建出来

然后可以二分斜率第一个大于它,倒过来第一个大于它的,也就是凸包的上下两个点

如果这两个点在直线异侧,那么直线穿过凸包


[ZJOI2008]瞭望塔 

解:比较裸的半平面交,首先可以把合法的瞭望塔塔顶范围求出

如果下面建在顶点,那么最小高度就是正上方与合法区域的交点

如果下面建在边上,那么最小高度就是在这条边正上方的合法区域的最下面那个点

近期刷题总结----计算几何_第1张图片


[SCOI2007]最大土地面积 

题意:求一个最大的四边形面积

解:很明显在凸包上选四个点,考虑固定一个点,另一个点转一圈,然后两个两个指针分别移动到最优点,这样是O(n^2)

然后发现一个点定后,去到最优的对面那个点一定是对踵点,然后可以先来一遍求对踵点,然后一个点动,对踵点动,两头的指针分别动,这样移动是O(n) 的


[HAOI2008]下落的圆盘

题解:考虑每个圆,枚举它后面出现的圆,然后维护被盖住的角

被盖住的圆弧所对的角可以用余弦定理解一下,然后把所有被盖住的区间合并

可以把被盖住的区间表示成[l,r] (与圆心的夹角),然后按左端点排序,扫一遍即可


[BZOJ1249] 动态凸包 

解:跟求凸包是一样的,只需要每次二分斜率,求某个点的前驱,后继

然后叉积判一下该不该弹


[WOJ4629] Oil 

类似前面某道题,直线可以通过选转转到端点上

于是可以枚举一个端点,与所有的线段,一个线段要在过当前端点的直线中被穿过,必须满足一个斜率区间[l, r]

然后就是一堆区间求一个 max, 于是左端点排序然后扫一遍

注意这里 y / x, x可能为0, 于是我们把斜率转换为 x / y, 并不影响答案


[HDU4773] Problem of Apollonius 

解:考虑在点 P 求一个圆的反演,然后做两个圆的公切线,在反演回去

注意圆心与P需要在公切线的同一侧,否则是内切不符合题意

反演时坐标的转换可能需要画个图解一下方程,不好写,放个代码

#include
#include
#include
using namespace std;
const double R = 5.0, eps = 1e-6;
int T, tot = 1;
struct Point{
	double x, y; 
	Point(double _x = 0, double _y = 0){ x = _x, y = _y;}
	Point operator + (const Point &a){ return Point(x + a.x, y + a.y);}
	Point operator - (const Point &a){ return Point(x - a.x, y - a.y);}
	Point operator * (const double &a){ return Point(x * a, y * a);}
	Point operator / (const double &a){ return Point(x / a, y / a);}
	double operator ^ (const Point &a){ return x * a.y - y * a.x;} 
}P;
double Dis(Point a, Point b){ 
	return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
struct Circle{
	Point o; double r;
	Point Getpos(double a){ return Point(o.x + r * cos(a), o.y + r * sin(a));}
} c[10];
int sign(double x){ if(fabs(x) < eps) return 0; if(x > 0) return 1; return -1;}
double Cross(Point a, Point b, Point c){ return ((b - a) ^ (c - a));}
Circle Inverse(Circle A){
	Circle B; 
	double OC1 = Dis(A.o, P);
	B.r = 0.5 * (1 / (OC1 - A.r) - 1 / (OC1 + A.r)) * R * R;
	double OC2 = R * R / (OC1 + A.r) + B.r;
	B.o = P +  (A.o - P) * OC2 / OC1;
	return B;
}
void Mark(Point a, Point b){
	double tmp = fabs(Cross(a, b, P)) / Dis(a, b);
	c[++tot].r = 0.5 * R * R / tmp;
	c[tot].o = P + (a - c[0].o) * c[tot].r / Dis(a, c[0].o);
}
void Solve(){
	c[0] = Inverse(c[0]); c[1] = Inverse(c[1]);
	if(c[0].r < c[1].r) swap(c[0], c[1]);
	Point tmp = c[1].o - c[0].o;
	double alpha = atan2(tmp.y, tmp.x);
	double beta = acos((c[0].r - c[1].r) / Dis(c[0].o, c[1].o));
	Point P1 = c[0].Getpos(alpha + beta);
	Point P2 = c[1].Getpos(alpha + beta);
	if(sign(Cross(P1, P2, P)) == sign(Cross(P1, P2, c[0].o))
	&& sign(Cross(P1, P2, P)) == sign(Cross(P1, P2, c[1].o))) Mark(P1, P2);
	P1 = c[0].Getpos(alpha - beta);
	P2 = c[1].Getpos(alpha - beta);
	if(sign(Cross(P1, P2, P)) == sign(Cross(P1, P2, c[0].o))
	&& sign(Cross(P1, P2, P)) == sign(Cross(P1, P2, c[1].o))) Mark(P1, P2);
}
int main(){
	scanf("%d", &T);
	while(T--){ tot = 1;
		for(int i = 0; i < 2; i ++) scanf("%lf%lf%lf", &c[i].o.x, &c[i].o.y, &c[i].r);
		scanf("%lf%lf", &P.x, &P.y); Solve();
		printf("%d\n", tot - 1);
		for(int i = 2; i <= tot; i++) printf("%.8lf %.8lf %.8lf\n", c[i].o.x, c[i].o.y, c[i].r); 
	} return 0;
}

 


[BZOJ2961] 共点圆 

可以圆的反演,然后转换为半平面交问题,也可以暴力推式子

加设一个点(x,y) 在圆心为(X, Y)的圆中,需要满足

(X-x) ^ 2+ (Y-y)^2<= X^2 + Y^2 , 也就是

-2x*X+x^2+y^2<=2y*Y

-\frac{x}{y}*X+\frac{x^2+y^2}{2y}<=Y

把 - x/y 看做 k,(x^2 + y^2) / 2y 看做 b,那么就是 Y >= kX + b

于是维护(X, Y)的上凸包,如何维护?

考虑分治,直接建出[l, mid] 的凸包,然后查询[mid+1, r] 的点, 查询二分一下斜率就可以了

#include
#define N 500050
using namespace std;
const double eps = 1e-8;
struct Point{
	double x, y; 
	Point(double _x = 0, double _y = 0){ x = _x, y = _y;}
	Point operator + (const Point &a){ return Point(x + a.x, y + a.y);}
	Point operator - (const Point &a){ return Point(x - a.x, y - a.y);}
	double operator ^ (const Point &a){ return x * a.y - y * a.x;} 
}p[N], sta[N];
bool cmp(Point a, Point b){ return (a.x < b.x) || (a.x == b.x && a.y < b.y);}
struct Node{ int op; Point p;} q[N];
int n, ans[N], siz[N], top;
int sign(double a){ if(fabs(a) < eps) return 0; if(a > 0) return 1; return -1;}
void Gramham(int m){
	top = 0;
	if(m == 1){ sta[++top] = p[1]; return;}
	sort(p + 1, p + m + 1, cmp);
	sta[++top] = p[1];
	for(int i = 2; i <= m; i++){
		if(sign(p[i].x - sta[top].x)){
			while(top > 1 && sign((p[i] - sta[top-1]) ^ (sta[top] - sta[top-1])) > 0) top--;
			sta[++top] = p[i];	
		}
	}
}
int Getpos(double k){ 
	int l = 1, r = top - 1;
	while(l < r){
		int mid = (l+r) >> 1;
		Point now = sta[mid + 1] - sta[mid];
		if(now.y / now.x >= k) r = mid;
		else l = mid + 1;
	} return l;
}
double sqr(double x){ return x * x;}
void Solve(int l, int r){
	if(l == r) return;
	int mid = (l+r) >> 1;
	Solve(l, mid); Solve(mid + 1, r);
	int tot = 0, res = 0;
	for(int i = l; i <= mid; i++) if(!q[i].op) p[++tot] = q[i].p;
	for(int i = mid+1; i <= r; i++) if(q[i].op && ans[i]) res++;
	if(!tot || !res) return;
	Gramham(tot);
	for(int i = mid+1; i <= r; i++){
		if(q[i].op && ans[i]){
			Point now = q[i].p; 
			int pos = Getpos(-now.x / now.y);
			ans[i] &= (sqr(sta[pos].x - now.x) + sqr(sta[pos].y - now.y) <= sqr(sta[pos].x) + sqr(sta[pos].y));
			ans[i] &= (sqr(sta[top].x - now.x) + sqr(sta[top].y - now.y) <= sqr(sta[top].x) + sqr(sta[top].y));	
		}
	} 
}
int main(){
	scanf("%d", &n);
	for(int i = 1; i <= n; i++){
		scanf("%d%lf%lf", &q[i].op, &q[i].p.x, &q[i].p.y);
		siz[i] = siz[i-1] + (!q[i].op); ans[i] = (siz[i] > 0);
	} Solve(1, n);
	for(int i = 1; i <= n; i++) if(q[i].op){
		if(ans[i]) printf("Yes\n"); else printf("No\n");
	} return 0;
}

 

你可能感兴趣的:(计算几何,凸包,半平面交,旋转卡壳)