计算几何小结

本文总结了计算几何领域的多种题型及其解题方法,包括点线相交、方向路径问题、正方形组合、扫描线算法、凸包问题、旋转卡壳、半平面交以及圆的并集等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

计算几何的题目也做了挺多的了,一直没想过写个总结

今天刷题刷累了,就写个总结吧

先给出我的做题过程:

http://acm.hust.edu.cn/vjudge/contest/view.action?cid=63632#overview

把这个专题里的题一个个地做完,就差不多了。这个专题需要有一定计算几何基础

先说下计算几何题目的类型大致可以分为下面几类:

点、线的相交,方向、路径问题,正方形、矩形的组合问题,矩形的面积并(扫描线),凸包,旋转卡壳,半平面交,圆的并


点、线的相交、方向、路径:

这都是些基本问题,随便搜一下教程都一大堆了,就不说了


正方形组合:

至于正方形、矩形的组合问题,很关键的一个思想就是点的hash,思想参考这篇博文:http://blog.youkuaiyun.com/lyy289065406/article/details/6647405

很不错的博文,解决了一直困扰我很久的问题啊


扫描线:

关于扫描线的问题,有n^3、n^2、nlogn这几种做法

n^3做法,就是根据所有的x、y值,把所有矩形拆成一个个的小矩形,然后分别累加

n^2做法,就是根据所有的x值,把所有矩形拆成一个个竖条的小矩形,然后分别累加

而nlogn做法,则是在n^2的做法上运用了线段树


凸包:

凸包的做法有很,网上通行是就是graham扫描了,大致思路就是找到最下的点,然后根据该点对其他点进行排序,然后沿着逆时针或顺时针方向扫过去,当存在已知点不符合凸包的要求时就回退


旋转卡壳:

先给出一个不错的学习链接:http://blog.youkuaiyun.com/hanchengxi/article/details/8639476

这篇博文还是写得很全面的,对思路的学习很有帮助,可惜没有实现代码

旋转卡壳可以用来解决的问题挺多的,包括平面最远点对,两多边形最远点对,两多边形最近点对

对于平面最远点对,基本思路就是先求凸包,找出最远点对分布的可能位置,再在凸包上进行旋转卡壳

而两多边形最远点对、最近点对,则是利用有方向的旋转卡壳进行求解


半平面交:

半平面交解决的问题就是:对一个凹多边形,在其内部求一个区域,使区域的每一个点,都可以连一条直线到多边形边界上任意一点。(其实就是求一些直线相交,围成的面积)

半平面交有两种做法,一种是n^2复杂度的,一种是nlogn的做法

n^2的思路还是比较好理解,用每一条边去切割多边形,每次切割之后再在新的基础上进行切割,所有边都切割一次后,剩下的多边形即为所求

至于nlog的做法网上基本上找不到教程,思路可以参考朱泽园的半平面交的论文,而网上实现的代码都是对朱泽园的思路进行过优化的,具体思路就是先对所有边按极角排序,以保证交出来的点都是有序的,然后按照某一方向求直线相交,若交点不在已围成的面积内则回退,如此继续下去,直到遍历完每一条直线

这里给出一个普通半平面交不错的模板:http://blog.youkuaiyun.com/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;
}

写完这篇小结才发现自己会的原来这么多了,不容易啊!!!总算是不枉费我寒假天天在刷题!!!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值