计算几何的题目也做了挺多的了,一直没想过写个总结
今天刷题刷累了,就写个总结吧
先给出我的做题过程:
http://acm.hust.edu.cn/vjudge/contest/view.action?cid=63632#overview
把这个专题里的题一个个地做完,就差不多了。这个专题需要有一定计算几何基础
先说下计算几何题目的类型大致可以分为下面几类:
点、线的相交,方向、路径问题,正方形、矩形的组合问题,矩形的面积并(扫描线),凸包,旋转卡壳,半平面交,圆的并
点、线的相交、方向、路径:
这都是些基本问题,随便搜一下教程都一大堆了,就不说了
正方形组合:
至于正方形、矩形的组合问题,很关键的一个思想就是点的hash,思想参考这篇博文:http://blog.csdn.net/lyy289065406/article/details/6647405
很不错的博文,解决了一直困扰我很久的问题啊
扫描线:
关于扫描线的问题,有n^3、n^2、nlogn这几种做法
n^3做法,就是根据所有的x、y值,把所有矩形拆成一个个的小矩形,然后分别累加
n^2做法,就是根据所有的x值,把所有矩形拆成一个个竖条的小矩形,然后分别累加
而nlogn做法,则是在n^2的做法上运用了线段树
凸包:
凸包的做法有很,网上通行是就是graham扫描了,大致思路就是找到最下的点,然后根据该点对其他点进行排序,然后沿着逆时针或顺时针方向扫过去,当存在已知点不符合凸包的要求时就回退
旋转卡壳:
先给出一个不错的学习链接:http://blog.csdn.net/hanchengxi/article/details/8639476
这篇博文还是写得很全面的,对思路的学习很有帮助,可惜没有实现代码
旋转卡壳可以用来解决的问题挺多的,包括平面最远点对,两多边形最远点对,两多边形最近点对
对于平面最远点对,基本思路就是先求凸包,找出最远点对分布的可能位置,再在凸包上进行旋转卡壳
而两多边形最远点对、最近点对,则是利用有方向的旋转卡壳进行求解
半平面交:
半平面交解决的问题就是:对一个凹多边形,在其内部求一个区域,使区域的每一个点,都可以连一条直线到多边形边界上任意一点。(其实就是求一些直线相交,围成的面积)
半平面交有两种做法,一种是n^2复杂度的,一种是nlogn的做法
n^2的思路还是比较好理解,用每一条边去切割多边形,每次切割之后再在新的基础上进行切割,所有边都切割一次后,剩下的多边形即为所求
至于nlog的做法网上基本上找不到教程,思路可以参考朱泽园的半平面交的论文,而网上实现的代码都是对朱泽园的思路进行过优化的,具体思路就是先对所有边按极角排序,以保证交出来的点都是有序的,然后按照某一方向求直线相交,若交点不在已围成的面积内则回退,如此继续下去,直到遍历完每一条直线
这里给出一个普通半平面交不错的模板:http://blog.csdn.net/accry/article/details/6070621
这个模板里直线是用一般式表示的,由两点的表示转为一般式的方法为:
a = y.y - x.y;
b = x.x - y.x;
c = y.x * x.y - x.x * y.y;
再推荐一个不错的题:http://poj.org/problem?id=1755
这里把一个解不等式的题目转化为了半平面交,很巧妙
圆的并:
圆的并这道题,上海热身赛的时候碰到过,当时根本听都没听说过,赤裸裸的模板题。
回来之后搜了一下,找到这篇大牛博客:http://hi.baidu.com/aekdycoin/blog/item/c1b28e3711246b3f0b55a95e.html
大致思路就是先对每个圆对其他所有圆求交点,与其他每个圆的两个交点中,一个点标志为入点,一个点为出去,这里运用了扫描线时会用到的思想,再把该圆上的交点按角度排序,可以统计出每段圆弧被覆盖的次数,然后把所有只覆盖一次的点连起来,围成的面积就是覆盖两次及以上的面积,再加上对应的弓形(即只覆盖一次的面积)就可以了。具体做法就是对每段只覆盖一次的圆弧,求出对应弓形面积及对应多边形的一条边的有向面积,累加起来就可以了
上面那篇博客里的实现代码很难看懂,这里给出一个好理解的代码:里面area[i]为覆盖i次及以上的面积,对应圆的并即为area[i]
/*
题目:CIRU2
题目来源:SPOJ 8119 https://www.spoj.pl/problems/CIRUT/
题目难度:中等偏难
题目内容或思路:
圆的面积并
题意:给n个圆(最多1000个),分别求出覆盖1层的面积、覆盖2层的面积、
覆盖3层的面积。。。覆盖n层的面积
方法见AC大牛blog:
http://hi.baidu.com/aekdycoin/blog/item/c1b28e3711246b3f0b55a95e.html
做题日期:2011.3.27
*/
#include <cstdio>
#include <cstdlib>
#include <climits>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <queue>
#include <map>
#include <vector>
#include <bitset>
#include <cmath>
#include <set>
#include <utility>
#include <ctime>
#define sqr(x) ((x)*(x))
using namespace std;
const int N = 1010;
const double eps = 1e-8;
const double pi = acos(-1.0);
double area[N];
int n;
int dcmp(double x) {
if (x < -eps) return -1; else return x > eps;
}
struct cp {
double x, y, r, angle;
int d;
cp(){}
cp(double xx, double yy, double ang = 0, int t = 0) {
x = xx; y = yy; angle = ang; d = t;
}
void get() {
scanf("%lf%lf%lf", &x, &y, &r);
d = 1;
}
}cir[N], tp[N * 2];
double dis(cp a, cp b) {
return sqrt(sqr(a.x - b.x) + sqr(a.y - b.y));
}
double cross(cp p0, cp p1, cp p2) {
return (p1.x - p0.x) * (p2.y - p0.y) - (p1.y - p0.y) * (p2.x - p0.x);
}
int CirCrossCir(cp p1, double r1, cp p2, double r2, cp &cp1, cp &cp2) {//两圆求交点数 ,并用cp1和cp2存储其交点
double mx = p2.x - p1.x, sx = p2.x + p1.x, mx2 = mx * mx;
double my = p2.y - p1.y, sy = p2.y + p1.y, my2 = my * my;
double sq = mx2 + my2, d = -(sq - sqr(r1 - r2)) * (sq - sqr(r1 + r2));//sq为两圆距离的平方和
if (d + eps < 0) return 0; if (d < eps) d = 0; else d = sqrt(d);
double x = mx * ((r1 + r2) * (r1 - r2) + mx * sx) + sx * my2;
double y = my * ((r1 + r2) * (r1 - r2) + my * sy) + sy * mx2;
double dx = mx * d, dy = my * d; sq *= 2;
cp1.x = (x - dy) / sq; cp1.y = (y + dx) / sq;
cp2.x = (x + dy) / sq; cp2.y = (y - dx) / sq;
if (d > eps) return 2; else return 1;
}
bool circmp(const cp& u, const cp& v) {
return dcmp(u.r - v.r) < 0;
}
bool cmp(const cp& u, const cp& v) {
if (dcmp(u.angle - v.angle)) return u.angle < v.angle;
return u.d > v.d;
}
double calc(cp cir, cp cp1, cp cp2) {
double ans = (cp2.angle - cp1.angle) * sqr(cir.r)
- cross(cir, cp1, cp2) + cross(cp(0, 0), cp1, cp2);
return ans / 2;
}
void CirUnion(cp cir[], int n) {
cp cp1, cp2;
sort(cir, cir + n, circmp);
for (int i = 0; i < n; ++i)
for (int j = i + 1; j < n; ++j)
if (dcmp(dis(cir[i], cir[j]) + cir[i].r - cir[j].r) <= 0)
cir[i].d++;//某圆被覆盖的次数
for (int i = 0; i < n; ++i) {
int tn = 0, cnt = 0;
for (int j = 0; j < n; ++j) {
if (i == j) continue;
if (CirCrossCir(cir[i], cir[i].r, cir[j], cir[j].r,
cp2, cp1) < 2) continue;
cp1.angle = atan2(cp1.y - cir[i].y, cp1.x - cir[i].x);//求圆心指向交点的向量的极角,注意这里atan2(y,x)函数要先y后x
cp2.angle = atan2(cp2.y - cir[i].y, cp2.x - cir[i].x);
//
cp1.d = 1; tp[tn++] = cp1;
cp2.d = -1; tp[tn++] = cp2;
if (dcmp(cp1.angle - cp2.angle) > 0) cnt++;
}
tp[tn++] = cp(cir[i].x - cir[i].r, cir[i].y, pi, -cnt);//第i个圆的左端点
tp[tn++] = cp(cir[i].x - cir[i].r, cir[i].y, -pi, cnt);
sort(tp, tp + tn, cmp);//把交点按极角排序
int p, s = cir[i].d + tp[0].d;
for (int j = 1; j < tn; ++j) {
p = s; s += tp[j].d;
printf("%f %f %f %f\n",tp[j-1].x,tp[j].y,tp[j-1].x,tp[j].y);
area[p] += calc(cir[i], tp[j - 1], tp[j]);//所有被覆盖了p次的圆弧的弦连起来形成的面积,等于覆盖了p次及p次以上的面积,可画图观察
}
}
}
void solve() {
for (int i = 0; i < n; ++i)
cir[i].get();
memset(area, 0, sizeof(area));
CirUnion(cir, n);
for (int i = 1; i <= n; ++i) {
area[i] -= area[i + 1];
printf("[%d] = %.3lf\n", i, area[i]);
}
}
int main() {
#ifndef ONLINE_JUDGE
freopen("in.txt", "r", stdin);
#endif
while (scanf("%d", &n) != EOF) {
solve();
}
return 0;
}
写完这篇小结才发现自己会的原来这么多了,不容易啊!!!总算是不枉费我寒假天天在刷题!!!