题目
题目背景
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>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cctype>
#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))
PDD getTangent(Circle &a, Circle &b){
llong vx = a.x-b.x, vy = a.y-b.y;
if(!vx && !vy) return PDD{-M_PI,M_PI};
if(a.r == b.r){
double theta = atan2(-vx,vy);
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;
if(v_len_2(nx,ny) > 1-EPS)
return PDD{-M_PI,M_PI};
ratio = sqrt(1/v_len_2(nx,ny)-1);
double dx = ratio*ny, dy = -ratio*nx;
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 ;
PDD qx = getTangent(circ[a],circ[b]);
if(qx.first > qx.second) std::swap(qx.first,qx.second);
int core;
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)
return void(v.push_back(PDI{r,core}));
if(r <= qx.first || qx.second <= l)
return void(v.push_back(PDI{r,a^b^core}));
if(r <= qx.second){
v.push_back(PDI{qx.first,a^b^core});
v.push_back(PDI{r,core});
}
else if(qx.first <= l){
v.push_back(PDI{qx.second,core});
v.push_back(PDI{r,a^b^core});
}
else{
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;
}
else{
calc(lst,rc[j].first,lc[i].second,rc[j].second,res);
lst = rc[j].first, ++ j;
}
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;
}
|