计算几何学习笔记(一):二维计算几何

平面几何基本知识

这部分是小学 / 初中 / 高中知识。

平面直角坐标系:在同一个平面上互相垂直且有公共原点 \(O\) 的两条数轴 \(x, y\) 构成平面直角坐标系。

对于平面内任意一点 \(A\),过点 \(A\) 分别向 \(x\) 轴、\(y\) 轴作垂线,垂足在 \(x\) 轴、\(y\) 轴上的对应点 \(a, b\) 分别叫做点 \(A\) 的横坐标、纵坐标,有序数对 \((a, b)\) 叫做点 \(A\) 的直角坐标。

平面极坐标系:在平面上选一定点 \(O\),称为极点;

自极点引出一条射线 \(Ox\),称为极轴;

选择一个单位长度(在数学问题中通常为 \(1\)),一个角度单位(通常为弧度)及其正方向(通常为逆时针方向);

就建立了极坐标系。

\(A\) 为平面上一点。

  • 极点 \(O\)\(A\) 之间的距离 \(|OA|\) 称为极径,记为 \(\rho\)

  • 以极轴为始边,\(OA\) 为终边的角 \(\angle xOA\) 称为极角,记为 \(\varphi\)

那么有序数对 \((\rho,\varphi)\) 即为 \(A\) 的极坐标。

向量:见线性代数学习笔记(一):线性代数基础知识

点积:不用矩阵表示。已知两个向量 \(\boldsymbol a, \boldsymbol b\),它们的夹角为 \(\theta\),那么它们的点积 \(\boldsymbol a \cdot \boldsymbol b = |\boldsymbol a||\boldsymbol b| \cos \theta\)

叉积:不用矩阵表示。已知两个向量 \(\boldsymbol a, \boldsymbol b\),它们的夹角为 \(\theta\),那么它们的叉积 \(\boldsymbol a \times \boldsymbol b = |\boldsymbol a||\boldsymbol b| \sin \theta\)

点与直线基础问题

接下来的知识和高中数学有所不同,信息学更喜欢用向量来计算。

判断向量夹角关系

根据 \(\cos\) 的性质,可以判断两个向量 \(\boldsymbol a\)\(\boldsymbol b\) 的夹角关系:

  1. \(\boldsymbol a \cdot \boldsymbol b = 0\),两向量垂直;

  2. \(\boldsymbol a \cdot \boldsymbol b < 0\),夹角小于 \(90^{\circ}\)

  3. \(\boldsymbol a \cdot \boldsymbol b > 0\),夹角大于 \(90^{\circ}\)

判断向量位置关系

根据 \(\sin\) 的性质,可以判断两个向量 \(\boldsymbol a\)\(\boldsymbol b\) 的位置关系:

  1. \(\boldsymbol a \times \boldsymbol b = 0\),两向量共线;

  2. \(\boldsymbol a \times \boldsymbol b < 0\)\(\boldsymbol b\)\(\boldsymbol a\) 的顺时针方向;

  3. \(\boldsymbol a \times \boldsymbol b > 0\)\(\boldsymbol b\)\(\boldsymbol a\) 的逆时针方向。

向量旋转

假设我们要将 \(\boldsymbol v = (a, b)\) 逆时针旋转 \(\theta\) 度。根据向量的三角表示的运算,可以得到旋转后的向量 \(\boldsymbol v' = (a \cos \theta - b \sin \theta, b \cos \theta + a \sin \theta)\)

判断线段是否相交

首先特判一些特殊情况。如果两线段平行,自然不能相交。这种情况通过判断线段所在直线的斜率是否相等即可。

对于其他情况,我们先介绍以下这种判断方法。

快速排斥实验:规定「一条线段的区域」为以这条线段为对角线的,各边均与某一坐标轴平行的矩形所占的区域,那么可以发现,如果两条线段没有公共区域,则这两条线段一定不相交。

注意到如果两条线段有公共区域,它们也有可能不相交,因此还有另一种判断直线是否相交的办法。

跨立实验:如果对于两线段 \(AB, CD\)\(CD\) 线段的两个端点分布在 \(AB\) 线段所在直线的两侧,且 \(AB\) 线段的两个端点分布在 \(CD\) 线段所在直线的两侧,我们就称即两线段相交。

具体的,我们从 \(A\) 的向 \(C\)\(D\) 的各连一条线段,再将其看成向量。如果 \(\overrightarrow{AC} \times \overrightarrow{AB}\)\(\overrightarrow{AD} \times \overrightarrow{AB}\) 异号,那么就可以说明 \(C\)\(D\)\(AB\) 两侧。

注意到当两条线段共线但不相交时也可以通过跨立实验,但是这样就不能通过快速排斥实验。因此如果两条线段既通过了快速排斥实验,又通过了跨立实验,那么这两条直线就相交了。

求线段交点

\(AB, CD\) 交于点 \(E\),那么 \(\overrightarrow{OE} = \overrightarrow{OA} + \displaystyle\frac{\overrightarrow{AC} \times \overrightarrow{CD}}{\overrightarrow{CD} \times \overrightarrow{AB}} \times \overrightarrow{AB}\),通过等面积法可以得证。

注意:如果两线段不交,这个式子也会返回它们所在直线的交点,此时需要通过上一小节的知识特判。

还有一点,如果 \(\overrightarrow{AB}\)\(\overrightarrow{CD}\) 共线,那么分母会变成 \(0\),此时也需要特判。

点击查看代码
Point Intersection(Line a, Line b){
  Vector x = a.t - a.s, y = b.t - b.s, z = a.s - b.s;
  return a.s + x * (Cross(y, z) / Cross(x, y));
}

判断点是否在直线左侧

点到线段的距离

假设我们要求点 \(C\) 到线段 \(AB\) 的距离。首先,如果 \(\triangle ABC\) 是钝角三角形且 \(C\) 为锐角顶点,那么 \(C\) 到线段 \(AB\) 的距离就是 \(\min(AC, BC)\),需要特判。

接下来,依然使用等面积法,根据叉积的几何含义,可以得到 \(d = \displaystyle\frac{|\overrightarrow{AC} \times \overrightarrow{BC}|}{|\overrightarrow{AB}|}\)

点到直线的垂足

设过 \(C\)\(AB\) 作垂线垂足为 \(H\)。根据点积的几何含义,可以得到 \(\overrightarrow{AH} = \displaystyle\frac{|\overrightarrow{AC} \cdot \overrightarrow{AB}|}{|AB|^2} \overrightarrow{AB}\)

多边形基础问题

多边形周长

直接计算即可。

多边形面积

设多边形顶点按顺时针排列为:\(\boldsymbol{a_1} = (x_1, y_1), \boldsymbol{a_2} = (x_2, y_2), \dots, \boldsymbol{a_n} = (x_n, y_n)\),那么这个多边形的面积 \(S = \displaystyle\frac 12 \left(\boldsymbol{a_n} \times \boldsymbol{a_1} + \sum_{i = 1}^{n - 1} \boldsymbol{a_i} \times \boldsymbol{a_{i + 1}} \right)\)

证明:

点击查看代码
double area(){
	n = p.size();
	double res = 0;;
	for(int i = 0; i < n; i++)
		res += (Cross(p[i], p[(i + 1) % n]));
	res /= 2;
	return res;
}

判断点在多边形内

一个可以感性理解的方法。我们考虑以该点为端点引出一条射线,如果这条射线与多边形有奇数个交点,则该点在多边形内部,否则该点在多边形外部。根据若尔当曲线定理,这条射线每次与多边形的一条边相交,就切换一次与多边形的内外关系,所以统计交点数的奇偶就可以了。

点击查看代码

判断点在凸多边形内

此时的多边形变成了凸的,此时我们就有了更简单的方法。

一个非常简单的思路为,我们直接花费 \(\mathcal O(n)\) 的时间复杂度,枚举凸包上的每条边,用叉积判断这个点是否在该边的左侧。

还有一种方法是,钦定一个顶点,二分出它可能在以这个顶点为顶点,多边形上的边为底边的哪个三角形内,看它是否在那条边左侧即可。不过值得注意的是,它可能不在任何一个三角形中,需要特判。

点击查看代码
bool inco(Point A, vector <Point> &a){
	int l = 0, r = a.size() - 1;
	while(l < r){
		int mid=(l + r + 1) >> 1;
		if(sgn(Cross(a[mid] - a[0], A - a[0])) >= 0)
			l = mid;
		else
			r = mid - 1;
	}
	if(l == (int)a.size() - 1){
		if(sgn(Cross(A - a[0], A - a[l])) == 0 && sgn(a[0].x - A.x) <= 0 && sgn(A.x - a[l].x) <= 0 && sgn(a[0].y - A.y) <= 0 && sgn(A.y - a[l].y) <= 0)
			return 1;
		return 0;
	} else if(Cross(a[l + 1] - a[l], A - a[l]) >= 0)
		return 1;
	else
		return 0;
}

经典例题

洛谷 P4468 [SCOI2007] 折纸

洛谷 P4529 [SCOI2003] 切割多边形

距离

欧几里得距离

曼哈顿距离

切比雪夫距离

三角剖分

自适应辛普森法

自适应辛普森法是一种精度比较高的拟合积分的方法。

洛谷 P4525 【模板】自适应辛普森法 1

一般我们求积分都是通过矩形去拟合,如图所示:

但是经过大量的实验(?),我们发现如果在计算机上使用这种方法去求积分,精度比较低,因此我们发扬人类智慧,用二次函数去拟合原函数。

首先,我们根据多项式的积分,可以得到 \(\displaystyle\int ax^2 + bx + c \mathrm dx = \frac a3 x^3 + \frac b2 x^2 + cx + C\),我们将其设为 \(g(x)\),表示该二次函数下的面积。

和用矩形去拟合函数一样,我们将 \([l, r]\) 分成若干个小区间,每个小区间用二次函数拟合原函数 \(f(x)\) 在这一区间下的面积,再求和,可以得到 \(\displaystyle\int_l^r g(x) \mathrm dx = \int_l^r \frac a3 x^3 + \int_l^r \frac b2 x^2 + \int_l^r cx = \frac{a (r^3 - l^3)}{3} + \frac{b (r^2 - l^2)}{2} + c(r - l)\)

不过我们发现这个式子似乎和 \(g(x)\) 没有太大关系,因此我们胡乱化简,看看能不能在分子处凑出 \(g(x)\) 的形式,结果还真化简成功了:\(\displaystyle\frac{a (r^3 - l^3)}{3} + \frac{b (r^2 - l^2)}{2} + c(r - l) = \frac{(r - l) \left[a l^2 + bl + c + a r^2 + br + c + 4 \left(\frac{a l^2 + 2alr + a r^2}{4} + \frac{bl + br}{2} + c\right) \right]}{6} = \frac{(r - l) \left[g(l) + g(r) + 4 g \left(\frac{l + r}{2} \right) \right]}{6}\)

由于我们是用 \(g(x)\) 来拟合 \(f(x)\),因此 \(g(x) \approx f(x)\),因此 \(\displaystyle\frac{(r - l) \left[g(l) + g(r) + 4 g \left(\frac{l + r}{2} \right) \right]}{6} \approx \frac{(r - l) \left[f(l) + f(r) + 4 f \left(\frac{l + r}{2} \right) \right]}{6}\),也就是 \(f(x)\)\([l, r]\) 这一段的近似积分啦。

但是这样精度还是不够,因此我们不断将 \([l, r]\) 拆分成 \([l, mid]\)\([mid + 1, r]\),将两边分别求积分,再加起来就可以了。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const double eps = 1e-6;
double a, b, c, d, L, R;
double f(double x) {
	return (c * x + d) / (a * x + b);
}
double simpson(double l, double r) {
	return (f(r) + f(l) + 4 * f((l + r) / 2)) * (r - l) / 6;
}
double getans(double l, double r) {
	double mid = (l + r) / 2;
	if(fabs(r - l) <= eps)
		return simpson(l, r);
	else
		return getans(l, mid) + getans(mid, r);
}
int main(){
	scanf("%lf%lf%lf%lf%lf%lf", &a, &b, &c, &d, &L, &R);
	printf("%.6lf", getans(L, R));
	return 0;
}

洛谷 P4526 【模板】自适应辛普森法 2

我觉得这道题目是难在判断收敛性上,而不是代码上。

无穷界积分的审敛法

无穷间断点的审敛法

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const double eps = 1e-8;
double a, L, R;
double f(double x) {
	return pow(x, a / x - x);
}
double simpson(double l, double r) {
	return (f(r) + f(l) + 4 * f((l + r) / 2)) * (r - l) / 6;
}
double getans(double l, double r, double ans) {
	double mid = (l + r) / 2, lans = simpson(l, mid), rans = simpson(mid, r);
	if(fabs(lans + rans - ans) <= eps)
		return ans;
	else
		return getans(l, mid, lans) + getans(mid, r, rans);
}
int main(){
	scanf("%lf", &a);
	L = eps, R = 20;
	if(a < 0)
		printf("orz");
	else
		printf("%.5lf", getans(L, R, simpson(L, R)));
	return 0;
}

凸包

我觉得这应该算是计算几何中最重要的一个知识点了,不光对于计算几何很重要,对于 DP 也非常重要。

定义

满足能围住平面上一些点的最小凸多边形。

凸多边形就是内角都小于 \(180^{\circ}\)

\(\text{Andrew}\) 算法

感觉和用单调栈维护决策单调性差不多

首先把所有点以横坐标为第一关键字,纵坐标为第二关键字排序。显然,此时纵坐标最大和最小的点都应该出现在凸包上。

我们只考虑如何求出上凸壳,下凸壳同理。由于上凸壳中每个线段的斜率是单调递减的,于是我们用一个栈来维护凸壳上的点,每次新加入一个点 \(P\),如果 \(P\) 与栈顶点连线的斜率大于栈顶点与次栈顶点联线的斜率,那么我们就将栈顶弹出,直到满足条件或者栈内只有一个点。

洛谷 P2742 [USACO5.1] 圈奶牛Fencing the Cows /【模板】二维凸包

点击查看代码
int n, sta[N], top;
void Convex_hull(){
	sort(p + 1, p + n + 1);
	for(int i = 1; i <= n; i++){
		while(top >= 2 && sgn(Cross(p[sta[top]] - p[sta[top - 1]], p[i] - p[sta[top]])) <= 0)
			top--;
		sta[++top] = i;
	}
	int tmp = top;
	for(int i = n - 1; i >= 1; i--){
		while(top > tmp && sgn(Cross(p[sta[top]] - p[sta[top - 1]], p[i] - p[sta[top]])) <= 0)
			top--;
		sta[++top] = i;
	}
	if(n > 1)
		top--;
}

\(\text{Graham}\) 扫描法

另外一种求凸包的方法,不需要求两次。

我们先按照所有点的纵坐标排序,那么纵坐标最小的那个点 \(P\) 一定在凸包上。

我们考虑在凸包上逆时针走,那么我们一定是不断地在左拐,也就是说,对于凸包上任意 \(3\) 个连续的点 \(P_1, P_2, P_3\),都有 \(\overrightarrow{P_1 P_2} \times \overrightarrow{P_2 P_3} \geq 0\)。因此我们可以仿照 \(\text{Andrew}\) 算法的流程,拿一个栈来维护当前凸包上的点,然后按照当前点与 \(P\) 的极角大小从小到大尝试将每一个点加入凸包。如果新加入的点让凸包右拐了,那么我们就将栈顶弹出,直到满足条件或者栈内只有一个点。

洛谷 P2742 [USACO5.1] 圈奶牛Fencing the Cows /【模板】二维凸包

点击查看代码
int n, sta[N], top;
void Convex_hull(){
	for(int i = 2; i <= n; i++)
		if(sgn(p[i].y - p[1].y) < 0 || (sgn(p[i].y - p[1].y) == 0 && sgn(p[i].x - p[1].x) < 0))
	    	swap(p[1], p[i]);
	for(int i = 2; i <= n; i++)
	    p[i].ang = atan2(p[i].y - p[1].y, p[i].x - p[1].x);
	sort(p + 2, p + n + 1, cmp);
	sta[++top] = 1;
	for(int i = 2; i <= n; i++){
	    while(top >= 2 && sgn(Cross(p[sta[top]] - p[sta[top - 1]], p[i] - p[sta[top]])) < 0)
	      top--;
	    sta[++top] = i;
	}
}

闵可夫斯基和

闵可夫斯基和在信息学中主要用来合并两个凸包。

对于点集 \(A\)\(B\),定义它们的闵可夫斯基和 \(A + B = \{(X_A + X_B, Y_A + Y_B) | (X_A, Y_A) \in A, (X_B, Y_B) \in B\}\),相当于 \(\boldsymbol x + \boldsymbol y\)

现在告诉你点集 \(A\) 的和点集 \(B\),需要求出点集 \(A + B\) 的凸包。

首先,新凸包上的点一定是原来的两个凸包上的点加出来的,因为原来不在凸包上的点在新图中也会被包进去,因此我们只用考虑 \(A\)\(B\) 凸包上的点。

在这里,闵可夫斯基和有一个重要的性质,那就是如果点集 \(A\) 和点集 \(B\) 是凸集,那么 \(A + B\) 也是凸集。此时我们需要证明对于两个点 \(e, f \in A + B\)\(e, f\) 之间的线段上的每个点都在 \(A + B\) 内。

于是我们设 \(e = a + c, f = b + d\)\(a, b \in A\)\(c, d \in B\)。根据平面向量的知识,我们知道 \(e, f\) 之间线段上的点可以表示成 \(te + (1 - t)f\)\(t \in [0, 1]\),推下式子,可以得到 \(te + (1 - t)f = t(a + c) + (1 - t)(b + d) = ta + (1 - t)b + [tc + (1 - t)d]\),此时我们发现 \(ta + (1 - t)b\)\(A\) 中,而 \(tc + (1 - t)d\)\(B\) 中,因此 \(te + (1 - t)f\) 一定在 \(A + B\) 中,证明成立。

现在我们已经知道了 \(A + B\) 可以形成一个新的凸包,但是这个凸包的边和点我们还不知道,因此求不出周长与面积这些信息,因此我们还要确定这个凸包的点和边的信息。这里,闵可夫斯基和还有一个重要的性质,那就是如果点集 \(A\) 和点集 \(B\) 是凸集,那么 \(A + B\) 的边集是由凸集 \(A, B\) 的边集按极角排序后连接的结果。

现在我们来证明这个性质。首先我们旋转坐标轴使得凸集 \(A\)\(B\) 中的一条边 \(XY\) 平行于 \(x\) 轴,假设是 \(A\)。再假设凸集 \(A\) 中所有边和凸集 \(B\) 中所有边的斜率都不一样,这样 \(B\) 中就只会有一个最低点 \(U\)

考虑到闵可夫斯基和相当于向量加法,因此 \(A + B\) 中最低且最靠左的点 \(P\) 一定是由当前 \(A\) 中最低且最靠左的点加上 \(B\) 中最低且最靠左的点得到的,因此 \(\overrightarrow{OP} = \overrightarrow{OX} + \overrightarrow{OU}\)。同理,\(A + B\) 中最低且最靠右的点 \(Q\) 满足 \(\overrightarrow{OQ} = \overrightarrow{OY} + \overrightarrow{OU}\)

此时 \(A + B\) 中最低的边就是 \(\overrightarrow{PQ} = \overrightarrow{OQ} - \overrightarrow{OP} = \overrightarrow{OY} + \overrightarrow{OU} - \overrightarrow{OX} - \overrightarrow{OU} = \overrightarrow{XY}\),此时我们发现 \(A + B\) 中的每一条边都是由 \(A\)\(B\) 中每一条边平移而来,而我们不断旋转坐标系,相当于是将凸集 \(A, B\) 的边集按极角排序,这就证明了这个性质。

于是我们将 \(A\) 集合中的每条边 \(i\)\(B\) 集合中的每条边 \(j\) 都用向量表示出来,再按照归并排序的思路将两个集合中的边 按极角合并起来。这里有一个小技巧,那就是我们不需要真正的求出极角,而只需要判断 \(\boldsymbol i \times \boldsymbol j\) 的符号即可。如果 \(\boldsymbol i \times \boldsymbol j \geq 0\),那么 \(\boldsymbol i\) 就在 \(\boldsymbol j\) 的顺时针方向,此时 \(\boldsymbol i\) 的极角就比 \(\boldsymbol j\) 小,否则 \(\boldsymbol j\) 的极角就更小。

最后,我们再沿着每条边,求出每个点的坐标。注意此时点的坐标是无序的,还需要再做一遍求凸包的算法才能得到这个凸包。

点击查看代码
vector <Point> Minkowski(vector <Point> &a, vector <Point> &b){
	int n = a.size(), m = b.size();
	vector <Point> c, tmp1, tmp2;
    c.resize(n + m + 1);
	c[0] = a[0] + b[0];
	tmp1.resize(n + 1);
	tmp2.resize(m + 1);
    for(int i = 1; i < n; i++)
		tmp1[i] = a[i] - a[i - 1];
	tmp1[n] = a[0] - a[n - 1];
    for(int i = 1; i < m; i++)
		tmp2[i] = b[i] - b[i - 1];
	tmp2[m] = b[0] - b[m - 1];
	int i = 1, j = 1, k = 0;
    while(i <= n && j <= m){
    	if(sgn(Cross(tmp1[i], tmp2[j])) >= 0)
			c[++k] = tmp1[i++];
		else
			c[++k] = tmp2[j++];
	}
    while(i <= n)
		c[++k] = tmp1[i++];
    while(j <= m)
		c[++k] = tmp2[j++];
	for(int i = 1; i <= k; i++)
		c[i] = c[i] + c[i - 1];
    return c;
}

动态凸包

动态加点

CF70D Professor's task

点击查看代码

动态删点

洛谷 P2521 [HAOI2011] 防线修建

点击查看代码

凸包在 DP 优化中的作用

经典例题

洛谷 P3829 [SHOI2012] 信用卡凸包

点击查看代码

洛谷 P4557 [JSOI2018] 战争

点击查看代码

旋转卡壳

这是一种求解凸包信息时常用的算法,通过名字就可以看出,它相当于是那两个尺子将凸包卡住,再不断的旋转来解决问题。

求凸包直径

洛谷 P1452 【模板】旋转卡壳 | [USACO03FALL] Beauty Contest G

首先,我们通过任意一种求凸包的算法将凸包求出。

接着,我们逆时针枚举每条边,找到离这条直线最远的点 \(x\),然后取这条直线两个端点中离 \(x\) 更远的那个点来更新答案。注意到我们逆时针枚举每条边时,\(x\) 也会逆时针旋转,因此可以直接双指针。

具体的,如果我们求出了距离第 \(i\) 条直线最远的点 \(j\),我们要求出距离第 \(i + 1\) 条直线最远的点,此时我们就判断一下 \(j\) 与第 \(i\) 条直线形成的三角形和 \(j + 1\) 与第 \(i\) 条直线形成的三角形大小,如果后者大就继续比较即可。

注意,我们需要在栈顶再次加入 \(1\) 号节点,使得可以计算 \(1\)\(n\) 这条边。

点击查看代码
void get_diameter(){
	if(top < 4) {
		ans = sqrdis(p[sta[1]], p[sta[2]]);
		return;
	}
	for(int i = 1, j = 3; i < top; i++){
	    while(area(sta[i], sta[i + 1], sta[j]) <= area(sta[i], sta[i + 1], sta[j % top + 1]))
	      	j = j % top + 1;
	    ans = max(ans, max(sqrdis(p[sta[i + 1]], p[sta[j]]), sqrdis(p[sta[i]], p[sta[j]])));
	}
}

最小矩形覆盖

洛谷 P3187 [HNOI2007] 最小矩形覆盖

点击查看代码

半平面交

点击查看代码
int n, m, cnt, st = 1, ed = 0;
vector <Line> l, l2, q;
vector <Point> p;
void Half_Plain_Intersection(){
	int n = l.size();
	sort(l.begin(), l.end());
	l2.push_back(l[0]);
	for(int i = 1; i < n; i++)
		if(sgn(ang(l[i].t - l[i].s) - ang(l[i - 1].t - l[i - 1].s)) != 0)
			l2.push_back(l[i]);
	n = l2.size();
	q.resize(n + 10);
	for(int i = 0; i < n; i++){
		while(st + 1 <= ed && !lft(Intersection(q[ed], q[ed - 1]), l2[i]))
			ed--;
		while(st + 1 <= ed && !lft(Intersection(q[st], q[st + 1]), l2[i]))
			st++;
		q[++ed] = l2[i];
	}
	while(st + 1 <= ed && !lft(Intersection(q[ed], q[ed - 1]), q[st]))
		ed--;
	q[++ed] = q[st];
	p.clear();
	for(int i = st; i < ed; i++)
		p.push_back(Intersection(q[i], q[i + 1]));
}

平面最近点对

圆相关问题

随机增量法

圆反演

参考资料

posted @ 2025-08-18 14:38  Orange_new  阅读(58)  评论(0)    收藏  举报