RE:从零开始的计算几何生活

RE:从零开始的计算几何生活

计算几何算法汇总

爱来自 yyc。

定义一些坏文明:

#define db double
const db eps = 1e-10;

误差计算:

int sign (double x) {
	return x > eps ? 1 : (x < -eps ? -1 : 0); 
}

向量:

struct vec {
	double x, y;
    void debug () { printf("%.3lf %.3lf\n", x, y); }
    vec operator + (const vec & t) const { return vec{x + t.x, y + t.y}; }
    vec operator - (const vec & t) const { return vec{x - t.x, y - t.y}; }
    vec operator * (const double & t) const { return vec{x * t, y * t}; }
    vec operator / (const double & t) const { return vec{x / t, y / t};}
    double len () { return sqrt(x * x + y * y); }
    double operator | (const vec & t) const {
		return x * t.x + y * t.y; 
	}
    double operator ^ (const vec & t) const {
		return x * t.y - y * t.x;
    }
} ;

数量积:

double operator | (const vec & t) const {
	return x * t.x + y * t.y; 
}

叉积:

double operator ^ (const vec & t) const {
	return x * t.y - y * t.x;
}

几何意义是两个向量围成的平行四边形的有向面积。如果是正的那么 ba 逆时针转过来的,那么 a×b=a.xb.ya.yb.x

img

应该这样来说:

yaxa>ybxbxa×ybxb×ay<0 ,所以叉积 <0 就说明方向向量 a 对应的直线斜率更大。

凸包

按照水平顺序排序。

考虑增量构造法。

怎样一个点才会被加入凸包。那么如果加入一个点的时候,可以包住以前的点显然才会加入凸包。

所以使用单调栈维护栈顶和栈顶底下的点就可以维护一个凸包乐。

如果成乐顺时针夹角,那么就弹掉栈顶加点。这样必然可以求得下凸壳。

乐。

通过!

#include <bits/stdc++.h>
#define rep(i, l, r) for (int i = l; i <= r; i ++)
#define per(i, r, l) for (int i = r; i >= l; i --)
#define db double

using namespace std;
typedef long long ll;

const int _ = 1e5 + 5, mod = 998244353;

const db eps = 1e-10;
int n, stk[_], top;
int sign (double x) {
	return x > eps ? 1 : (x < -eps ? -1 : 0); 
}
struct vec {
	double x, y;
    void debug () { printf("%.3lf %.3lf\n", x, y); }
    vec operator + (const vec & t) const { return vec{x + t.x, y + t.y}; }
    vec operator - (const vec & t) const { return vec{x - t.x, y - t.y}; }
    vec operator * (const double & t) const { return vec{x * t, y * t}; }
    vec operator / (const double & t) const { return vec{x / t, y / t};}
    double len () { return sqrt(x * x + y * y); }
    double operator | (const vec & t) const {
		return x * t.x + y * t.y; 
	}
    double operator ^ (const vec & t) const {
		return x * t.y - y * t.x;
    }
    bool operator == (const vec & t) { return !sign(x - t.x) && !sign(y - t.y); }
} p[_];
bool cmp (vec a, vec b) { return sign(a.x - b.x) == 0 ? a.y < b.y : a.x < b.x; }
double dis (vec x, vec y) {
	vec z = x - y;
	return z.len(); 
}
bool antilock (vec x, vec y, vec z) { return sign((x - z) ^ (y - z)) >= 0; }
int main() {
	/*
	freopen(".in", "r", stdin);
	freopen(".out", "w", stdout);
	黛拉可玛莉·岗德森布莱德,一亿年一遇美少女。
	*/
	cin >> n;
	rep(i, 1, n) scanf("%lf%lf", & p[i].x, & p[i].y);
	sort(p + 1, p + 1 + n, cmp);
	n = unique(p + 1, p + 1 + n) - (p + 1);
	stk[1] = 1, stk[top = 2] = 2;
	rep(i, 3, n) {
		while(top >= 2 && !antilock(p[i], p[stk[top]], p[stk[top - 1]]))
			top --;
		stk[++ top] = i;
	} 
	double ret = 0;
	rep(i, 1, top - 1) ret += dis(p[stk[i]], p[stk[i + 1]]);
	stk[1] = n, stk[top = 2] = n - 1;
	per(i, n - 2, 1) {
		while(top >= 2 && !antilock(p[i], p[stk[top]], p[stk[top - 1]]))
			top --;
		stk[++ top] = i;
	}
	rep(i, 1, top - 1) ret += dis(p[stk[i]], p[stk[i + 1]]);
	printf("%.2lf", ret);
	return 0;
}

旋转卡壳

凸包内最远点对 直径

定义凸包上的对踵点对 : 用两条平行直线卡着凸包转,着两条直线一定会卡住至少两个点,这两个点称为对踵点对。

img

旋 转 卡 壳。

引理(重要) : 只用考虑斜率恰好与凸包某条边相同的直线。

证明:感觉证明法。

考虑最远点也是随着边旋转的,所以边走边跑双指针即可。

注意维护点到直线的距离,可以使用叉积加面积法解决,但是这里固定乐一个边。

注意不能保留共线点即可。

#include <bits/stdc++.h>
#define rep(i, l, r) for (int i = l; i <= r; i ++)
#define per(i, r, l) for (int i = r; i >= l; i --)
#define db double

using namespace std;
typedef long long ll;

const int _ = 1e5 + 5, mod = 998244353;

const db eps = 1e-7;
int n, stk[_], top;
int sign (double x) {
	return x > eps ? 1 : (x < -eps ? -1 : 0); 
}
struct vec {
	double x, y;
    void debug () { printf("%.3lf %.3lf\n", x, y); }
    vec operator + (const vec & t) const { return vec{x + t.x, y + t.y}; }
    vec operator - (const vec & t) const { return vec{x - t.x, y - t.y}; }
    vec operator * (const double & t) const { return vec{x * t, y * t}; }
    vec operator / (const double & t) const { return vec{x / t, y / t};}
    double len () { return sqrt(x * x + y * y); }
    double operator | (const vec & t) const {
		return x * t.x + y * t.y; 
	}
    double operator ^ (const vec & t) const {
		return x * t.y - y * t.x;
    }
    bool operator == (const vec & t) { return !sign(x - t.x) && !sign(y - t.y); }
} p[_];
bool cmp (vec a, vec b) { return sign(a.x - b.x) == 0 ? a.y < b.y : a.x < b.x; }
double dis (vec x, vec y) {
	vec z = x - y;
	return z.len(); 
}
bool antilock (vec x, vec y, vec z) { return sign((x - z) ^ (y - z)) > 0; }
double su (vec x, vec y, vec z) { return abs((x - y) ^ (x - z)); }

int tot;
vec v[_];
void hull () {
	rep(i, 1, n) scanf("%lf%lf", & p[i].x, & p[i].y);
	sort(p + 1, p + 1 + n, cmp);
	stk[1] = 1, stk[top = 2] = 2;
	rep(i, 3, n) {
		while(top >= 2 && !antilock(p[i], p[stk[top]], p[stk[top - 1]]))
			top --;
		stk[++ top] = i;
	} 
	rep(i, 1, top) v[++ tot] = p[stk[i]];
	stk[1] = n, stk[top = 2] = n - 1;
	per(i, n - 2, 1) {
		while(top >= 2 && !antilock(p[i], p[stk[top]], p[stk[top - 1]]))
			top --;
		stk[++ top] = i;
	}
	rep(i, 2, top - 1) v[++ tot] = p[stk[i]];
}
double RotateHull () {
	double ans = 0;
	if (tot == 2) { ans = dis(v[1], v[2]); return ans; }
	v[0] = v[tot];
	int cur = 2;
	rep(i, 1, tot) {
		while(su(v[cur % tot + 1], v[i], v[i - 1]) > eps + su(v[cur], v[i], v[i - 1]))
			cur = cur % tot + 1;
		ans = max(ans, dis(v[cur], v[i]));
		ans = max(ans, dis(v[cur], v[i - 1]));
	}
	return ans;
}
int main() {
	/*
	freopen(".in", "r", stdin);
	freopen(".out", "w", stdout);
	黛拉可玛莉·岗德森布莱德,一亿年一遇美少女。
	*/
	cin >> n;
	hull();
	double diameter = RotateHull();
	printf("%.0lf", diameter);
	return 0;
}

Minkowoski

{a+b|aA,bB}

具体来说就是把 B 中的每个点当成向量,沿着这个走所到达的所有点集。

img

乐。

凸壳的话,两个凸壳的闵可夫斯基和是凸壳。

结论 : 将两个凸包上的边按照极角序顺次连接即可得到答案。

「JSOI2018战争」

题面略去。

考虑求 BA 的闵可夫斯基和,判定是不是在 x 上即可。

Theory

感觉会计算几何里面的没啥用!!/fn/fn

这里着重介绍 Max/Min-Add 卷积,但是感觉这个东西除了就是闵可夫斯基和没有什么其他的关系啊。

考虑一个 fi=maxki{gk+hik} 的形式,这个就是很显然的 Max-Add 卷积,这个东西在动态 DP 中我们见了很多次了,但是我们又有什么新的东西吗?

考虑到我们只会凸包的闵可夫斯基和,所以我们可以钦定 gh 是一个凸函数。

如果我们发现 (i,fi) 是一个凸包,那么我们就由定义知道这个 fi 进行差分后的数组是单调的,然后就可以进行操作了。

这样的话我们可以贪心归并 fg 数组就好了。复杂度是 O(n) 的。

vector<int> max_add_convolution(vector<int> a, vector<int> b) {
    for (int i = a.size() - 1; i >= 1; i--)
        a[i] -= a[i - 1];
    for (int i = b.size() - 1; i >= 1; i--)
        b[i] -= b[i - 1];
    vector<int> c(a.size() + b.size() - 1);
    c[0] = a[0] + b[0];
    merge(a.begin() + 1, a.end(), b.begin() + 1, b.end(), c.begin() + 1, greater<>());
    for (int i = 1; i < a.size() + b.size() - 1; i++)
        c[i] += c[i - 1];
    return c;
}

这个是我懒得写的板子,其实就是维护差分数组+归并转移就好了/ll

优化 DP

有些时候形状如 fi,j=maxji{fi1,j+wi,ij} 的东西满足 f,w 是凸函数,我们可以使用 Minkowoski 来 O(n) 转移一行,进而使用分治来求 f 值,可以做到 O(nlogn)

题目

*QOJ 5421 ICPC Nanjing 2022 H

考虑直接乱掏一个 DP:

fx,i=maxfx,ij+fv,j+wx,v×(kj)×j ,然后贡献是一个二次函数,容易知道 f 是一个凸函数,我们考虑维护差分数组。差分后只需要区间加等差数列就好了。

由于是单调的,所以刚好可以利用平衡树来维护,但是还没有完,可以使用启发式合并来维护,写法比较抽象的一点是他是爆改的平衡树,左右儿子完全反了。

Hint:维护差分数组。

gym103202l forged in the barrens

问你划分 k 段使得极差之和最大,k[1,n]

我们可以掏出来一个 dp:

f0/1/2,0/1/2,[l,r],k 表示 [l,r] 选了 k 段,左右没有东西 / 空了一段正贡献 / 负贡献。然后我们发现这个是一个取了 max(max,+) 卷积,猜他是有凸性的直接大力闵可夫斯基和就好了。

vector <int> Merge (vector <int> a, vector <int> b) {
	per(i, a.size() - 1, 1) a[i] -= a[i - 1];
	per(i, b.size() - 1, 1) b[i] -= b[i - 1];
	vector <int> c(a.size() + b.size() - 1);
	c[0] = a[0] + b[0];
	c = merge(a.begin() + 1, a.end(), b.begin() + 1, b.end(), c.begin() + 1, c.end() greater<int>());
	rep(i, 1, c[i].size() - 1) c[i] += c[i - 1];
	return c;
} 
vector <int> max (vector <int> a, vector <int> b) {
	int len = max(a.size(), b.size());
	while(a.size() < len) a.emplace_back(-inf);
	while(b.size() < len) b.emplace_back(-inf);
	rep(i, 0, len - 1) a[i] = max(a[i], b[i]);
	return a[i];
}
*SDWC2021 Day3T3 美丽的世界

考虑经典建图 xy

然后变为两边必须选一个,一个连通块,边数个点,一眼丁真鉴定为纯纯的基环树。

然后我们考虑这样就变成了 (ai)(bi) 这样的东西,而且是二元,严格难于 timeismoney /jy

然后观察到 (ai)(bi) 值相等可以看成反比例函数,所以我们可以幽默地发现任意两点的连边比这个点的权值大,凸函数,启动!一眼二阶导小于零,启发我们找到左下凸包上的点,然后只需要对于每个联通块启合并即求闵可夫斯基和即可。

有点智的。启发还是用拓扑排序去写基环树。

这个题的写法是正儿八经地计算几何写法。

【2021山东省队集训第一轮 Day3】美丽的世界 - 题目 - QDEZ Online Judge (ezoj.org.cn)

CF1019E Raining season

不是不会做,就是不会写/ll

考虑 ax+b(a,b) 随便想想都知道是让我们去维护一个凸包来求这个函数的最值。

然后考虑这个写点分没啥用,所以我们写边分,然后边分+ Minkowoski 就做完了.jpg。

Slope Trick

是不是应该先学 Slope Trick 再学 Minkowoski 的??

但是没啥事哈。这个东西也是维护凸函数用的。

slope trick 学习笔记 - jrxxx's blog - 洛谷博客 (luogu.com.cn)

动态凸包

不太想写。

半平面交

乐。

posted @   Cust10  阅读(71)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示