题意

给出两个线段的端点坐标,问:若以垂直方向不断洒水,这两个线段组成的图形最多容纳的水的体积是多少?

思路

2022年了,没有SPJ的计算几何题该图图了

题意是非常直观的,主要难点在于处理一些特殊的边界情况。个人的处理方案如下:

  • 判断某一线段的两个端点是否相等(线段退化成点),若存在该情况,输出$0$

  • 判断两条线段是否没有相交,若存在该情况,输出$0$

  • 判断是否有某一条线段与$x$轴平行,若存在该情况,输出$0$

  • 判断两个线段是否互相平行,若存在该情况,输出$0$

  • 最后剩下的就是最棘手的情况:判断有无如下图所示导致无法接到水的情况。若存在该情况,输出$0$​

POJ2826-P1


POJ2826-P2

对此,我的解决方案是:

  • 首先考虑二分,求出“若能装水”,则水平面所处的纵坐标$y_0$是多少?二分左端点设为$l_1,l_2$交点$M$的$y$坐标,右端点设置为$10005$。若$y=y’$与线段$l_1,l_2$均有交点,则$y’ < y_0$,若无交点或只有一个交点,则$y’>y_0$​,由此计算出水平面所在的纵坐标。
  • 其次,从端点间的位置关系进行求解,具体看代码:

为表述方便,设“顶点”代指某一线段中,纵坐标较大的点

1
2
3
4
5
6
7
8
9
10
11
//(图1红线,图2橙线)
//l1的顶点在l2的顶点下方,且经过l1顶点垂直与x轴向上无限延伸的射线l2有相交点
if(sgn(l1.e.y - l2.e.y) <= 0 && Line(v1, Point(v1.x, 10005)).segcrossseg(l2)){
printf("%.2f\n", 0 + eps);
continue;
}
//类似上一判断
else if(sgn(l2.e.y - l1.e.y) <= 0 && Line(v2, Point(v2.x, 10005)).segcrossseg(l1)){
printf("%.2f\n", 0 + eps);
continue;
}
  • 最后,利用叉积计算三角形面积,输出即可。

    此处由于精度问题,加上1e-6作为eps就过了,还是有SPJ​​比较舒服(

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// 2826.cpp
//#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <iomanip>
#define maxn 100055
//#define double long double
using namespace std;
int t;
const double eps = 1e-8;
int sgn(double x){
if(fabs(x) <= eps) return 0;
return x > 0 ? 1 : -1;
}
struct Point{
double x, y;
Point(){}
Point(double _x, double _y){ x = _x; y = _y; }
Point operator +(const Point &A){
return Point(x + A.x, y + A.y);
}
Point operator -(const Point &A){
return Point(x - A.x, y - A.y);
}
double operator *(const Point &A){
return x * A.x + y * A.y;
}
double operator ^(const Point &A){
return x * A.y - y * A.x;
}
bool operator ==(const Point &A){
return sgn(x - A.x) == 0 && sgn(y - A.y) == 0;
}
double len(){
return sqrt(x * x + y * y);
}
void input(){
//cin >> x >> y;
scanf("%lf%lf", &x, &y);
}
void print(){
printf("[Point] x:%.2f y:%.2f\n", x, y);
}
}os, ot, ts, tt;
struct Line{
Point s, e;
Line(){}
Line(Point _s, Point _e){ s = _s; e = _e; }
int segcrossseg(Line v) {
int d1 = sgn((e - s) ^ (v.s - s));
int d2 = sgn((e - s) ^ (v.e - s));
int d3 = sgn((v.e - v.s) ^ (s - v.s));
int d4 = sgn((v.e - v.s) ^ (e - v.s));
if ((d1 ^ d2) == -2 && (d3 ^ d4) == -2) return 2;
return (d1 == 0 && sgn((v.s - s) * (v.s - e)) <= 0) ||
(d2 == 0 && sgn((v.e - s) * (v.e - e)) <= 0) ||
(d3 == 0 && sgn((s - v.s) * (s - v.e)) <= 0) ||
(d4 == 0 && sgn((e - v.s) * (e - v.e)) <= 0);
}
Point crosspoint(Line v){
double a1 = (v.e - v.s) ^ (s - v.s);
double a2 = (v.e - v.s) ^ (e - v.s);
return Point((s.x * a2 - e.x * a1) / (a2 - a1), (s.y * a2 - e.y * a1) / (a2 - a1));
}
bool parallel(Line v){
return sgn((e - s) ^ (v.e - v.s)) == 0;
}
double cosVal(Line v){
double val = (e - s) * (v.e - v.s);
return val / (e - s).len() / (v.e - v.s).len();
}
}l1, l2;
bool check(double x){
Line v = Line(Point(-10005.0, x), Point(10005.0, x));
if(v.segcrossseg(l1) && v.segcrossseg(l2)) return true;
return false;
}
void cal(double x){
int a = x * 1000;
if(a % 10 >= 5) a += 5;
a /= 10;
double b = a / 100.0;
printf("%.2lf\n", b);
}
int main(void)
{
int T;
scanf("%d", &T);
while(T--){
os.input(); ot.input();
ts.input(); tt.input();
if(sgn(os.y - ot.y) > 0) swap(os, ot); l1 = Line(os, ot);
if(sgn(ts.y - tt.y) > 0) swap(ts, tt); l2 = Line(ts, tt);
if(os == ot || ts == tt || !l1.segcrossseg(l2)){
printf("%.2f\n", 0 + eps);
}
else if((l1.parallel(Line(Point(0, 0), Point(1, 0))) || l2.parallel(Line(Point(0, 0), Point(1, 0))))){
printf("%.2f\n", 0 + eps);
}
else if(l1.parallel(l2)){
printf("%.2f\n", 0 + eps);
}
else{
Point m = l1.crosspoint(l2);
double lf = m.y, rt = 10005.0;
while(sgn(rt - lf) > 0){
double mid = (lf + rt) * 0.5;
check(mid) ? lf = mid : rt = mid;
}
Point v1 = Line(Point(-10005.0, lf), Point(10005.0, lf)).crosspoint(l1);
Point v2 = Line(Point(-10005.0, lf), Point(10005.0, lf)).crosspoint(l2);
if(sgn(l1.e.y - l2.e.y) <= 0 && Line(v1, Point(v1.x, 10005)).segcrossseg(l2)){
printf("%.2f\n", 0 + eps);
continue;
}
else if(sgn(l2.e.y - l1.e.y) <= 0 && Line(v2, Point(v2.x, 10005)).segcrossseg(l1)){
printf("%.2f\n", 0 + eps);
continue;
}
else if(sgn(fabs((v1 - m) ^ (v2 - m)) * 0.5) == 0){
printf("%.2f\n", 0 + eps);
continue;
}
cal(fabs((v1 - m) ^ (v2 - m)) * 0.5 + 1e-6);
}
}
return 0;
}

测试数据

来源:POJ讨论版以及对拍

INPUT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
0 1 1 0
1 0 2 1

0 1 2 1
1 0 1 2

0 0 2 2
1 1 0 0

0 0 1 1
1 1 2 2

0 0 10 0
0 0 1 1

0 0 3 1
-1 -1 5 5

6259 2664 8292 9080
1244 2972 9097 9680

0 1 1 0
1 0 2 1

0 1 2 1
1 0 1 2

0 0 10 10
0 0 9 8

0 0 10 10
0 0 8 9

0 0 0 2
0 0 -3 2

1 1 1 4
0 0 2 3

1 2 1 4
0 0 2 3

0 0 1 1
0 0 1 2

0 0 -1 -1
0 0 1 -1

157 -19 -760 664
386 -75 -760 419

1 1 7 7
7 7 15 15

0 0 1 1
0 0 1 1

-10000 1 0 0
10000 1 0 0

0 0 10 1
0 0 1 4

0 1 1 8
0 8 5 0

2 2 0 0
0 5 1 1

OUTPUT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
1.00
0.00
0.00
0.00
0.00
0.00
6162.65
1.00
0.00
0.00
4.50
3.00
0.75
0.00
0.00
0.00
0.00
0.00
0.00
10000.00
4.88
0.65
0.63