题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6697
题意:现在在平面上有 n n n条线段,并且定义线段 x x x和线段 y y y的最短距离为在 x x x上任取一点和 y y y上任取一点,最小的两点之间距离为两线段的最短距离。
解题心得:
- 先分析复杂度, n n n的范围是 10000 10000 10000,如果直接 n 2 n^2 n2暴力加上 T T T组数据肯定会超时。这个时候可以按线段的左端点的 x x x排序,这样在按顺序枚举任意两条直线的时候如果右侧线段左端点 x x x值和左侧线段右端点 x x x值的差值大于目前找到的答案,这个时候可以跳出第二重循环。就是暴力的时候剪枝。
- 然后就是两个线段怎么求最小距离,设现在一个三维空间内有四个点,分别是
A
B
C
D
ABCD
ABCD,坐标分别是
A
(
x
1
,
y
1
,
z
1
)
A(x1, y1, z1)
A(x1,y1,z1),
B
(
x
2
,
y
2
,
z
2
)
B(x2, y2, z2)
B(x2,y2,z2),
C
(
x
3
,
y
3
,
z
3
)
C(x3, y3, z3)
C(x3,y3,z3),
D
(
x
4
,
y
4
,
z
4
)
D(x4, y4, z4)
D(x4,y4,z4),两条线段分别是
A
B
AB
AB和
C
D
CD
CD。设
P
,
Q
P,Q
P,Q分别为在
A
B
AB
AB和
C
D
CD
CD上的点,
P
,
Q
P,Q
P,Q可以用参数方程表示,
P
P
P可以表示为:
{
X
=
x
1
+
S
(
x
2
−
x
1
)
Y
=
y
1
+
S
(
y
2
−
y
1
)
Z
=
z
1
+
S
(
z
2
−
z
1
)
\left\{\begin{matrix} & X=x1+S(x2-x1) & \\ & Y=y1+S(y2-y1) & \\ & Z=z1+S(z2-z1) & \end{matrix}\right.
⎩⎨⎧X=x1+S(x2−x1)Y=y1+S(y2−y1)Z=z1+S(z2−z1)
Q
Q
Q可以表示为:
{
U
=
x
3
+
T
(
x
4
−
x
3
)
V
=
y
3
+
T
(
y
4
−
y
3
)
W
=
z
3
+
T
(
z
4
−
z
3
)
\left\{\begin{matrix} & U=x3+T(x4-x3) & \\ & V=y3+T(y4-y3) & \\ & W=z3+T(z4-z3) & \end{matrix}\right.
⎩⎨⎧U=x3+T(x4−x3)V=y3+T(y4−y3)W=z3+T(z4−z3)
参数T和S必须满足 0 < T < 1 0<T<1 0<T<1且 0 < S < 1 0<S<1 0<S<1这个时候 P , Q P,Q P,Q两点才在线段上,否则是在线段的延长线上。两点间的距离 P Q = ( X − U ) 2 + ( Y − V ) 2 + ( Z − W ) 2 PQ=\sqrt{(X-U)^2+(Y-V)^2+(Z-W)^2} PQ=(X−U)2+(Y−V)2+(Z−W)2,设距离的平方为 f ( s , t ) = [ ( x 1 − x 3 ) + S ( x 2 − x 1 ) − T ( x 4 − x 3 ) ] 2 + [ ( y 1 − y 3 ) + S ( y 2 − y 1 ) − T ( y 4 − y 3 ) ] 2 + [ ( z 1 − z 3 ) + S ( x 2 − x 1 ) + T ( z 4 − z 3 ) ] 2 f(s,t)=[(x1-x3)+S(x2-x1)-T(x4-x3)]^2+[(y1-y3)+S(y2-y1)-T(y4-y3)]^2+[(z1-z3)+S(x2-x1)+T(z4-z3)]^2 f(s,t)=[(x1−x3)+S(x2−x1)−T(x4−x3)]2+[(y1−y3)+S(y2−y1)−T(y4−y3)]2+[(z1−z3)+S(x2−x1)+T(z4−z3)]2
这个时候我们需要得到的是 f ( s , t ) f(s,t) f(s,t)的最小值,所以对 f ( s , t ) f(s,t) f(s,t)分别求对 s s s和 t t t的偏导数,并且令偏导数为 0 0 0,联立可以可以得到 { [ ( x 2 − x 1 ) 2 + ( y 2 − y 1 ) 2 + ( z 2 − z 1 ) 2 ] S − [ ( x 2 − x 1 ) ( x 4 − x 3 ) + ( y 2 − y 1 ) ( y 4 − y 3 ) + ( z 2 − z 1 ) ( z 4 − z 3 ) ] T = ( x 1 − x 2 ) ( x 1 − x 3 ) + ( y 1 − y 2 ) ( y 1 − y 3 ) + ( z 1 − z 2 ) ( z 1 − z 3 ) − [ ( x 2 − x 1 ) ( x 4 − x 3 ) + ( y 2 − y 1 ) ( y 4 − y 3 ) + ( z 2 − z 1 ) ( z 4 − z 3 ) ] S + [ ( x 4 − x 3 ) 2 + ( y 4 − y 3 ) 2 + ( z 4 − z 3 ) 2 ] T = ( x 1 − x 3 ) ( x 4 − x 3 ) + ( y 1 − y 3 ) ( y 4 − y 3 ) + ( z 1 − z 3 ) ( z 4 − z 3 ) \left\{\begin{matrix} & [(x2-x1)^2+(y2-y1)^2+(z2-z1)^2]S-[(x2-x1)(x4-x3)+(y2-y1)(y4-y3)+(z2-z1)(z4-z3)]T=(x1-x2)(x1-x3)+(y1-y2)(y1-y3)+(z1-z2)(z1-z3) & \\ & -[(x2-x1)(x4-x3)+(y2-y1)(y4-y3)+(z2-z1)(z4-z3)]S+[(x4-x3)^2+(y4-y3)^2+(z4-z3)^2]T=(x1-x3)(x4-x3)+(y1-y3)(y4-y3)+(z1-z3)(z4-z3) & \end{matrix}\right. {[(x2−x1)2+(y2−y1)2+(z2−z1)2]S−[(x2−x1)(x4−x3)+(y2−y1)(y4−y3)+(z2−z1)(z4−z3)]T=(x1−x2)(x1−x3)+(y1−y2)(y1−y3)+(z1−z2)(z1−z3)−[(x2−x1)(x4−x3)+(y2−y1)(y4−y3)+(z2−z1)(z4−z3)]S+[(x4−x3)2+(y4−y3)2+(z4−z3)2]T=(x1−x3)(x4−x3)+(y1−y3)(y4−y3)+(z1−z3)(z4−z3)
对于上式进行求解可以得到 S S S和 T T T的值,这个时候若 S S S和 T T T都满足 ( 0 , 1 ) (0, 1) (0,1)区间则可以直接得到 P , Q P,Q P,Q两点具体的值,这样就得到了 P Q PQ PQ的距离。
若 S , T S,T S,T的范围不满足条件,则可以先得到 A , B A,B A,B两点到线段 C D CD CD的距离 d i s 1 , d i s 2 dis1,dis2 dis1,dis2, C , D C,D C,D两点到线段 A B AB AB的距离 d i s 3 dis3 dis3, d i s 4 dis4 dis4,这时两线段的距离就是 m i n ( d i s 1 , d i s 2 , d i s 3 , d i s 4 ) min(dis1, dis2, dis3, dis4) min(dis1,dis2,dis3,dis4)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 3e5+100;
struct Seg {
double x1, x2, y1, y2;
bool operator < (const Seg&x) const {
return x1 < x.x1;
}
}seg[maxn];
int t, n;
double ans;
void init() {
ans = 2e18;
scanf("%d",&n);
for(int i=1;i<=n;i++) {
scanf("%lf%lf%lf%lf", &seg[i].x1, &seg[i].y1, &seg[i].x2, &seg[i].y2);
if(seg[i].x2 < seg[i].x1) {
swap(seg[i].x1, seg[i].x2);
swap(seg[i].y1, seg[i].y2);
}
}
sort(seg+1, seg+1+n);
}
bool checke(int x, int y) {
double dis1 = seg[x].x2;
double dis2 = seg[y].x1;
if(dis2 - dis1 > ans) return true;
return false;
}
double PointToSegDist(double x, double y, double x1, double y1, double x2, double y2)//点到直线距离
{
double cross = (x2 - x1) * (x - x1) + (y2 - y1) * (y - y1);
if (cross <= 0) return sqrt((x - x1) * (x - x1) + (y - y1) * (y - y1));
double d2 = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
if (cross >= d2) return sqrt((x - x2) * (x - x2) + (y - y2) * (y - y2));
double r = cross / d2;
double px = x1 + (x2 - x1) * r;
double py = y1 + (y2 - y1) * r;
return sqrt((x - px) * (x - px) + (py - y) * (py - y));
}
double get_dis(int x, int y) {
double k1, k2, k3, k4, a1, a2;
double x1 = seg[x].x1, x2 = seg[x].x2, y1 = seg[x].y1, y2 = seg[x].y2;
double x3 = seg[y].x1, x4 = seg[y].x2, y3 = seg[y].y1, y4 = seg[y].y2;
double z2 = 0, z1 = 0, z4 = 0, z3 = 0;//二维平面则z坐标没有使用
k1 = ((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) + (z2-z1)*(z2-z1));
k2 = -((x2-x1)*(x4-x3) + (y2-y1)*(y4-y3) + (z2-z1)*(z4-z3));
a1 = (x1-x2)*(x1-x3) + (y1-y2)*(y1-y3) + (z1-z2)*(z1-z3);
k3 = -((x2-x1)*(x4-x3) + (y2-y1)*(y4-y3) + (z2-z1)*(z4-z3));
k4 = (x4-x3)*(x4-x3) + (y4-y3)*(y4-y3) + (z4-z3)*(z4-z3);
a2 = (x1-x3)*(x4-x3) + (y1-y3)*(y4-y3) + (z1-z3)*(z4-z3);
double temp_k = k1;
k1 *= k3;
k2 *= k3;
a1 *= k3;
k3 *= temp_k;
k4 *= temp_k;
a2 *= temp_k;
double T = (a1-a2)/(k2-k4);
double S = (a1-k2*T)/k1;
if(S >=0 && S <= 1.0 && T >= 0 && T <= 1.0) {
double X = x1 + (x2-x1)*S;
double Y = y1 + (y2-y1)*S;
double Z = z1 + (z2-z1)*S;
double X1 = x3 + (x4-x3)*T;
double Y1 = y3 + (y4-y3)*T;
double Z1 = z3 + (z4-z3)*T;
return sqrt((X-X1)*(X-X1) + (Y-Y1)*(Y-Y1) + (Z-Z1)*(Z-Z1));
} else {
double dis1 = PointToSegDist(x1, y1, x3, y3, x4, y4);
double dis2 = PointToSegDist(x2, y2, x3, y3, x4, y4);
double dis3 = PointToSegDist(x3, y3, x1, y1, x2, y2);
double dis4 = PointToSegDist(x4, y4, x1, y1, x2, y2);
return min(min(dis1, dis2), min(dis3, dis4));
}
}
void solve() {
for(int i=1;i<=n;i++) {
for(int j=i+1;j<=n;j++) {
if(checke(i, j)) break;
else ans = min(ans, get_dis(i, j));
}
}
}
int main() {
// freopen("1.in.txt", "r", stdin);
scanf("%d", &t);
while(t--) {
init();
solve();
printf("%.9f\n", ans);
}
}