IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> [SkcRound]凸包板题 -> 正文阅读

[数据结构与算法][SkcRound]凸包板题

题目

题目背景
D D ( X Y X ) \sf DD(XYX) DD(XYX) 是一根标杆,永远地标志着战力天花板。

题意概要
给出若干个圆,随机一个角度 θ \theta θ,取倾斜角为 θ \theta θ 的直线 ? \ell ? 去 “卡壳”,问两条直线的期望距离。输出结果乘 π \pi π,精度误差不超过 1 0 ? 8 10^{-8} 10?8

数据范围与提示
n ? 5 × 1 0 5 n\leqslant 5\times 10^5 n?5×105,但时限 3 s \rm 3s 3s

思路

设圆心坐标为 { ( x i , y i ) } \{(x_i,y_i)\} {(xi?,yi?)},半径为 { r i } \{r_i\} {ri?},显然就是求
∫ ? π π max ? ( x i cos ? θ + y i sin ? θ + r i ) ? d θ \int_{-\pi}^{\pi}\max(x_i\cos\theta+y_i\sin\theta+r_i)\cdot \text{d}\theta ?ππ?max(xi?cosθ+yi?sinθ+ri?)?dθ

直接考虑每个圆作为最大值的区间,难以使用排序后求凸包算法,毕竟该式并非直线。但我的视野太狭窄了;本来就存在 分治 求凸包的方法,只是对于二维直线的处理有更简单的方式罢了。最终我只拿了 3 p t s 3\rm pts 3pts,约为 D D ( X Y X ) \sf DD(XYX) DD(XYX) 得分的 6 % 6\% 6%

类似于凸包,我们需要证明:区间数量是 O ( n ) \mathcal O(n) O(n)。这里的 “区间” 是指,使得上式取 max ? \max max 的圆(即最大值点)不变的 θ \theta θ 的区间。不妨限定 θ ∈ [ ? π , π ] \theta\in[-\pi,\pi] θ[?π,π]

只需要注意到一个简单的性质——这个性质是直线也具有的——两个不同的圆的 最大值点不互相跨越。也就是说,不存在单增序列 { θ 1 , θ 2 , θ 3 , θ 4 } \{\theta_1,\theta_2,\theta_3,\theta_4\} {θ1?,θ2?,θ3?,θ4?} 使得 θ 1 , θ 3 \theta_1,\theta_3 θ1?,θ3? 处的 max ? \max max 值在圆 a a a 处取得,而 θ 2 , θ 4 \theta_2,\theta_4 θ2?,θ4? max ? \max max 值在 b b b 处取得。因为 [ ? π , π ] [-\pi,\pi] [?π,π] 中,只有 θ \theta θ 恰好为两个圆的外公切线倾斜角时,才会让 max ? \max max 取值点变化;变化两次,中间夹住的区间就是某个圆的 max ? \max max 贡献范围,就不会互相跨越了。

只要有这个性质,就可以建立树形结构。形式化的证明:任意时刻,存在某个圆,其 max ? \max max 贡献范围是一个区间(而非多个)。因为圆 a a a 的两个区间之间,设存在圆 b b b 的区间,则圆 b b b 的所有区间都在此处(由上述性质)。要么 b b b 只有一个区间,得证;要么 b b b 在此处有至少两个区间,对这两个区间递归分析——圆只有 n n n 个,所以递归有出口,得证。

然后归纳法。将 max ? \max max 贡献范围是一个区间的圆删去,得到 ( n ? 1 ) (n{\rm-}1) (n?1) 个圆的情形,由归纳法,最多 2 ( n ? 1 ) 2(n{\rm-}1) 2(n?1) 个区间;然后将这一个区间加入,除去自己这一个区间,至多使得区间数量 + 1 +1 +1,即两侧本来是一个大区间、现在裂开了。于是最多 2 n 2n 2n 个区间,归纳得证。

外层套个分治,时间复杂度 O ( n log ? n ) \mathcal O(n\log n) O(nlogn) 。计算答案可以用开头的式子;另有一个神奇结论是,答案为 凸包的周长。不妨设这个凸包只由圆弧构成(直线部分视为曲率半径非常大的圆弧),设列向量 [ x ( θ ) y ( θ ) ] \begin{bmatrix}x(\theta)\\y(\theta)\end{bmatrix} [x(θ)y(θ)?] θ \theta θ 对应的最优解(凸包上的点),则答案为
∫ ? π π [ x ( θ ) y ( θ ) ] ? [ cos ? θ sin ? θ ] ? d θ = ∫ ? π π x ( θ ) ? d ( sin ? θ ) ? y ( θ ) ? d ( cos ? θ ) = [ x ( θ ) sin ? θ ? y ( θ ) cos ? θ ] ? π π + ∫ ? π π cos ? θ ? d y ( θ ) ? sin ? θ ? d x ( θ ) = ∫ ? π π [ d x ( θ ) d y ( θ ) ] ? [ ? sin ? θ cos ? θ ] \begin{aligned} &\int_{-\pi}^{\pi} \begin{bmatrix} x(\theta)\\ y(\theta) \end{bmatrix} \cdot \begin{bmatrix} \cos\theta\\ \sin\theta \end{bmatrix} \cdot\text{d}\theta\\ =& \int_{-\pi}^{\pi}x(\theta)\cdot\text{d}(\sin\theta)-y(\theta)\cdot\text{d}(\cos\theta)\\ =& \Big[x(\theta)\sin\theta-y(\theta)\cos\theta\Big]_{-\pi}^{\pi}+\int_{-\pi}^{\pi}\cos\theta\cdot\text{d}y(\theta)-\sin\theta\cdot\text{d}x(\theta)\\ =& \int_{-\pi}^{\pi} \begin{bmatrix} \text{d}x(\theta)\\ \text{d}y(\theta) \end{bmatrix} \cdot \begin{bmatrix} -\sin\theta\\ \cos\theta \end{bmatrix} \end{aligned} ===??ππ?[x(θ)y(θ)?]?[cosθsinθ?]?dθ?ππ?x(θ)?d(sinθ)?y(θ)?d(cosθ)[x(θ)sinθ?y(θ)cosθ]?ππ?+?ππ?cosθ?dy(θ)?sinθ?dx(θ)?ππ?[dx(θ)dy(θ)?]?[?sinθcosθ?]?

由定义可知 [ d x ( θ ) d y ( θ ) ] \begin{bmatrix}\text{d}x(\theta)\\\text{d}y(\theta)\end{bmatrix} [dx(θ)dy(θ)?] 是垂直于 θ \theta θ 方向的。所以最后这个式子,实际上是向量长度;这就等价于 ∮ ∣ l → ∣ = C \oint|\overrightarrow{l}|=C l =C 即周长。多么神奇!

代码细节

稍微聊聊,分治之后合并时,怎么求出两个圆各自的 max ? \max max 贡献区间。代码实现能力强的,可以立刻退出这篇没用的博客了。

设圆心分别为 m 1 , m 2 m_1,m_2 m1?,m2?,半径为 r 1 , r 2 r_1,r_2 r1?,r2? 。根据我们的分析,只需要解出两个临界点
m 1 → ? n → ∣ n → ∣ + r 1 = m 2 → ? n → ∣ n → ∣ + r 2 {\overrightarrow{m_1}\cdot\overrightarrow{n}\over|\overrightarrow{n}|}+r_1={\overrightarrow{m_2}\cdot\overrightarrow{n}\over|\overrightarrow{n}|}+r_2 n m1? ??n ?+r1?=n m2? ??n ?+r2?

我比较懒,直接令 ∣ n → ∣ = 1 |\overrightarrow{n}|=1 n =1,先求出 n → \overrightarrow{n} n ( m 1 → ? m 2 → ) (\overrightarrow{m_1}-\overrightarrow{m_2}) (m1? ??m2? ?) 上的投影向量 n 0 → \overrightarrow{n_0} n0? ?,再做垂线与单位圆相交。

实现时, n 0 → = r 2 ? r 1 ∣ m 1 → ? m 2 → ∣ 2 ( m 1 → ? m 2 → ) \overrightarrow{n_0}={r_2-r_1\over|\overrightarrow{m_1}-\overrightarrow{m_2}|^2}(\overrightarrow{m_1}-\overrightarrow{m_2}) n0? ?=m1? ??m2? ?2r2??r1??(m1? ??m2? ?),求向量长度的平方是简单的。偏移量 ∣ v ∣ = 1 ? ∣ n 0 → ∣ 2 |v|=\sqrt{1-|\overrightarrow{n_0}|^2} v=1?n0? ?2 ? 所以 ∣ v ∣ ∣ n 0 → ∣ = 1 ∣ n 0 → ∣ 2 ? 1 {|v|\over|\overrightarrow{n_0}|}=\sqrt{{1\over|\overrightarrow{n_0}|^2}-1} n0? ?v?=n0? ?21??1 ?,很容易得到。

吐槽一句: c m a t h \tt cmath cmath 很脆弱。不仅 s q r t ( 0 ) {\tt sqrt}(0) sqrt(0) 会得到 i n f \rm inf inf,而且 i n f \rm inf inf 0 0 0 又会得到 n a n \rm nan nan,完全搞不懂 😂

完整代码

我还是按照最初的计算式求的。

坑:当相邻区间对应的最优圆是同一个时,应当合并。我忘了这茬……但仍然过了……

#include <cstdio> // JZM yydJUNK!!!
#include <iostream> // XJX yyds!!!
#include <algorithm> // XYX yydLONELY!!!
#include <cstring> // (the STRONG LONG for loneliness)
#include <cctype> // DDG yydDOG & ZXY yydBUS !!!
#include <initializer_list>
#include <vector>
# define _USE_MATH_DEFINES
#include <cmath>
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define rep0(i,a,b) for(int i=(a); i!=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
	int a = 0, c = getchar(), f = 1;
	for(; !isdigit(c); c=getchar())
		if(c == '-') f = -f;
	for(; isdigit(c); c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int MAXN = 500005;
const double EPS = 1e-10;
struct Circle{ int x, y, r; };
typedef std::pair<double,double> PDD;
# define v_len_2(x,y) ((x)*(x)+(y)*(y))
/// @return the angle (radian) of inclination
PDD getTangent(Circle &a, Circle &b){
	llong vx = a.x-b.x, vy = a.y-b.y; // M1-M2
	if(!vx && !vy) return PDD{-M_PI,M_PI};
	if(a.r == b.r){ // special case
		double theta = atan2(-vx,vy); // (y,-x)
		if(theta > 0) return PDD{theta,theta-M_PI};
		else return PDD{theta,theta+M_PI};
	}
	double ratio = double(b.r-a.r)/v_len_2(vx,vy);
	double nx = ratio*vx, ny = ratio*vy; // fake
	if(v_len_2(nx,ny) > 1-EPS) // no solution
		return PDD{-M_PI,M_PI}; // cover all
	ratio = sqrt(1/v_len_2(nx,ny)-1); // reuse
	double dx = ratio*ny, dy = -ratio*nx; // (y,-x)
	PDD res = {atan2(ny+dy,nx+dx),atan2(ny-dy,nx-dx)};
	return res;
}

Circle circ[MAXN];
typedef std::pair<double,int> PDI;
typedef std::vector<PDI> vecset;
void calc(double l, double r, int a, int b, vecset &v){
	if(l >= r) return ; // empty range
	PDD qx = getTangent(circ[a],circ[b]); // common tangent
	if(qx.first > qx.second) std::swap(qx.first,qx.second);
	int core; ///< which one is inside
	if(qx.first < 0 && 0 < qx.second)
		core = (circ[a].x+circ[a].r > circ[b].x+circ[b].r) ? a : b;
	else core = (circ[a].x+circ[a].r < circ[b].x+circ[b].r) ? a : b;
	if(qx.first <= l && r <= qx.second) // in
		return void(v.push_back(PDI{r,core}));
	if(r <= qx.first || qx.second <= l) // out
		return void(v.push_back(PDI{r,a^b^core}));
	if(r <= qx.second){ // two parts
		v.push_back(PDI{qx.first,a^b^core});
		v.push_back(PDI{r,core});
	}
	else if(qx.first <= l){ // two parts
		v.push_back(PDI{qx.second,core});
		v.push_back(PDI{r,a^b^core});
	}
	else{ // three parts
		v.push_back(PDI{qx.first,a^b^core});
		v.push_back(PDI{qx.second,core});
		v.push_back(PDI{r,a^b^core});
	}
}
vecset solve(int l, int r){
	if(l == r) return vecset{PDI{M_PI,l}};
	vecset &&lc = solve(l,(l+r)>>1);
	vecset &&rc = solve((l+r+2)>>1,r);
	int lenl = int(lc.size()), lenr = int(rc.size());
	double lst = -M_PI; vecset res; res.clear();
	for(int i=0,j=0; i!=lenl&&j!=lenr; )
		if(lc[i].first < rc[j].first){
			calc(lst,lc[i].first,lc[i].second,rc[j].second,res);
			lst = lc[i].first, ++ i; // merge sort
		}
		else{
			calc(lst,rc[j].first,lc[i].second,rc[j].second,res);
			lst = rc[j].first, ++ j; // keep moving
		}
	return res;
}

double contribute(double l, double r, int id){
	double kx = sin(r)-sin(l), ky = cos(r)-cos(l);
	return kx*circ[id].x-ky*circ[id].y+(r-l)*circ[id].r;
}
int main(){
	int n = readint();
	rep(i,1,n){
		circ[i].x = readint(), circ[i].y = readint();
		circ[i].r = readint();
	}
	vecset &&convex = solve(1,n);
	double lst = -M_PI, ans = 0;
	for(int len=int(convex.size()),i=0; i!=len; ++i)
		ans += contribute(lst,convex[i].first,
			convex[i].second), lst = convex[i].first;
	printf("%.12f\n",ans);
	return 0;
}
  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-03-10 22:50:47  更:2022-03-10 22:55:18 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 13:42:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码