题目概述
按逆时针序给定一个简单凸N边形每个顶点的坐标x,y,求多边形中到所有边距离最远的点到边的距离
时限
5000ms/15000ms
输入
第一行正整数N,其后N行,每行两个整数x,y,输入到N=0结束
限制
3<=N<=100;0<=x,y<=10000
输出
每行一个浮点数,为所求最远点到边的距离,输出与标准答案误差在1e-5范围内皆视为正确
样例输入
4
0 0
10000 0
10000 10000
0 10000
3
0 0
10000 0
7000 1000
6
0 40
100 20
250 40
250 70
100 90
0 70
3
0 0
10000 10000
5000 5001
5
0 0
1000 0
1000 100
500 150
0 100
4
0 0
100 0
120 50
20 50
4
0 0
100 0
110 50
30 50
4
0 0
100 0
200 1000
150 1000
100
5000 0
5300 10
5590 30
5870 60
6140 100
6400 150
6650 210
6890 280
7120 360
7340 450
7550 550
7750 660
7940 780
9220 2060
9340 2250
9450 2450
9550 2660
9640 2880
9720 3110
9790 3350
9850 3600
9900 3860
9940 4130
9970 4410
9990 4700
10000 5000
9990 5300
9970 5590
9940 5870
9900 6140
9850 6400
9790 6650
9720 6890
9640 7120
9550 7340
9450 7550
9340 7750
9220 7940
7940 9220
7750 9340
7550 9450
7340 9550
7120 9640
6890 9720
6650 9790
6400 9850
6140 9900
5870 9940
5590 9970
5300 9990
5000 10000
4700 9990
4410 9970
4130 9940
3860 9900
3600 9850
3350 9790
3110 9720
2880 9640
2660 9550
2450 9450
2250 9340
2060 9220
780 7940
660 7750
550 7550
450 7340
360 7120
280 6890
210 6650
150 6400
100 6140
60 5870
30 5590
10 5300
0 5000
10 4700
30 4410
60 4130
100 3860
150 3600
210 3350
280 3110
360 2880
450 2660
550 2450
660 2250
780 2060
2060 780
2250 660
2450 550
2660 450
2880 360
3110 280
3350 210
3600 150
3860 100
4130 60
4410 30
4700 10
3
0 0
10000 0
10000 1
4
0 0
10000 0
10000 1
0 1
3
0 0
10000 1
9999 1
4
0 0
1 0
10000 1
9998 1
5
0 0
9999 2000
10000 7000
3334 6334
1 5000
3
0 0
1 0
0 1
3
9999 0
10000 0
10000 1
3
0 9999
1 10000
0 10000
3
10000 9999
10000 10000
9999 10000
6
1000 5500
2500 1000
6000 1500
8000 4000
6500 8500
3000 8000
6
100 350
101 349
6400 3440
6400 3441
1200 7250
1199 7249
12
1000 4700
2000 4000
3000 3800
4000 3900
5000 4100
6000 4500
6000 5501
5000 5899
4000 6101
3000 6199
2000 6001
1000 5299
100
9999 0
9995 158
9987 316
9976 475
9957 632
9936 789
9907 945
9874 1100
9837 1255
9794 1407
9747 1559
9697 1709
9639 1857
9578 2003
9513 2148
9443 2290
9366 2429
9289 2567
9205 2702
9116 2834
9023 2962
8927 3088
8829 3213
8724 3332
8617 3449
8506 3562
8391 3671
8273 3777
8150 3878
8026 3976
7898 4070
7769 4162
7635 4247
7499 4329
7360 4406
7219 4478
7076 4547
6930 4609
6784 4669
6634 4723
6484 4772
6332 4818
6178 4856
6023 4893
5867 4922
5711 4946
5553 4966
5396 4983
5237 4993
5079 4998
4920 4996
4762 4993
4603 4983
4446 4967
4288 4947
4131 4923
3976 4891
3821 4856
3667 4818
3515 4772
3365 4723
3216 4667
3069 4609
2923 4546
2780 4477
2639 4405
2501 4327
2364 4247
2230 4162
2100 4072
1972 3978
1847 3879
1727 3776
1609 3670
1493 3562
1382 3449
1275 3332
1170 3213
1072 3088
976 2962
883 2834
794 2702
711 2567
632 2430
558 2289
486 2148
421 2003
360 1857
302 1709
252 1559
205 1407
162 1255
123 1101
92 945
65 789
41 632
25 474
13 316
5 158
1 0
12
8118 5000
7687 6551
6534 7657
5000 8187
3399 7773
2233 6597
1849 5000
2310 3447
3431 2283
4999 1854
6533 2344
7691 3445
12
5000 0
6152 665
6680 1580
6680 2780
5956 4034
5158 4494
3838 4494
2959 3987
2441 3089
2441 1604
2825 939
4451 0
6
9493 6827
4123 8291
2764 8126
233 3849
4751 89
9261 3165
10
9950 7585
9384 8907
6918 9826
749 9053
64 8730
590 4225
1153 2481
1441 2127
3850 289
9659 814
11
9845 5904
6473 9556
5150 9835
4005 9959
2361 9496
225 5244
187 3223
1837 777
4075 322
7218 183
9375 581
10
9995 909
9990 4084
8777 9930
2993 9997
2051 9909
36 9601
543 2264
636 2005
1408 29
8413 439
100
0 102
1 89
2 77
3 66
4 56
5 47
6 39
7 32
8 26
9 21
10 17
11 14
12 12
14 11
17 10
21 9
26 8
32 7
39 6
47 5
56 4
66 3
77 2
89 1
102 0
9898 0
9911 1
9923 2
9934 3
9944 4
9953 5
9961 6
9968 7
9974 8
9979 9
9983 10
9986 11
9988 12
9989 14
9990 17
9991 21
9992 26
9993 32
9994 39
9995 47
9996 56
9997 66
9998 77
9999 89
10000 102
10000 9898
9999 9911
9998 9923
9997 9934
9996 9944
9995 9953
9994 9961
9993 9968
9992 9974
9991 9979
9990 9983
9989 9986
9988 9988
9986 9989
9983 9990
9979 9991
9974 9992
9968 9993
9961 9994
9953 9995
9944 9996
9934 9997
9923 9998
9911 9999
9898 10000
102 10000
89 9999
77 9998
66 9997
56 9996
47 9995
39 9994
32 9993
26 9992
21 9991
17 9990
14 9989
12 9988
11 9986
10 9983
9 9979
8 9974
7 9968
6 9961
5 9953
4 9944
3 9934
2 9923
1 9911
0 9898
0
样例输出
5000.000000
494.233641
34.542948
0.353553
74.813432
25.000000
25.000000
48.398611
4997.224535
0.499975
0.500000
0.000050
0.000100
2766.028167
0.292893
0.292893
0.292893
0.292893
3083.220719
1959.904162
1187.882680
2498.751386
2996.786615
2079.535060
3485.538259
4573.726867
4427.563188
4673.004358
5000.000000
讨论
计算几何,半平面交,以及二分思想,其实题目的意思就是问凸多边形最大的内切圆半径,思路上很直白,一看到特判要求一个浮点数,想到二分是没什么问题,二分的是半径,或者说是每条线沿着其可行域的方向偏移的距离,然后通过半平面交检验符合条件的圆心是否存在,如果存在,半径还能继续加大,否则就要缩小半径,直到符合题目要求为止
下面说一点实现的问题,数组规模需要很大,这点应该能想到,但是由于之前的题都没出现过类似问题因而被忽略了,题目说的规模上限是100,但如果数组仅仅开到100,上面的样例会有好几组都过不去,甚至程序都跑不完就爆炸了,半平面交中间过程会产生新的交点,而且每当一个点被判定在半平面外部时,他与其前后一个点所成直线和判定线可以各相交出一个点,也就是说,数据确实有可能越算越多,实际上要想拿下样例,存放点的数组至少需要254的大小才足够
另外,计算直线交点一定要精准,以前额都是采用点斜式方程组解出交点坐标,但是这样会导致浮点误差迅速累积,最终影响到交点的结果,尤其是在数据规模较大,点与点之间不算很远的时候,影响会格外明显,于是改用定比分点公式求,效率和精确性都上升不少,而且额也不用手算了
二分边界的确定,算是个剪枝吧,下界选0没办法,但是上界从5000开始足矣,因为题目给定的坐标范围只有0到10000,圆的半径最大也不过5000,那么就从5000开始好了,精确度上,题目说1e-5,那写上1e-5也就可以了,额为了和标准答案输出尽量接近写的1e-7,实际上这些多算的既显示不出来,又一点用也没有,浪费计算机资源而已
再有就是四个极大边界,一般为了防止出现半平面交不封闭,都会人为添加四条封住边界的边,对于这种题似乎用不上,因为关注的重点只是最后点集是否为空而已,因而额的代码里没有用到,同样也因为额没有线的结构定义,无法单独表示一条直线,只能用相邻的点表示线,如果添加上,就会出现两条奇怪的线,从无穷远处连到多边形上的某个点,不出错才怪
代码的写法依旧是比较紧密的(一团乱麻),希望搭配注释能不那么混乱,代码写成这样额自己都不想多看一眼,不过其实最长的几行都是从前面几行复制粘贴过来而已,因而出错概率很小
样例数据是2007年日本区域赛时的数据,题解代码已经调整过,保证其输出和标准答案完全一致,没有特判也能过
题解状态
184K,16MS,C++,3049B
题解代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
#define INF 0x3f3f3f3f
#define MAXN 300//数组大小100是不够的 至少需要254 半平面交是会让数据规模越算越大的
#define memset0(a) memset(a,0,sizeof(a))
#define EPS 1e-8
struct Pt//point 点的结构
{
double x, y;
}pts[MAXN], pt1[MAXN], pt2[MAXN];//第一个记录原始数据 后面两个是半平面交的辅助数组
int hpt1, hpt2;//halfplane_point 记录辅助数组中点的数量
int N;//给出的点的总数
double d[MAXN], ofx[MAXN], ofy[MAXN];//distance 记录原始数据中相邻两点的距离 方便求偏移量 offset_x offset_y 两个方向的偏移量 也就是所求半径体现在对每条直线水平和竖直方向位置的影响 一次求好放在数组中
double xp(double x1, double y1, double x2, double y2, double x3, double y3)//万年不变的向量积
{
return (x1 - x2)*(y3 - y2) - (y1 - y2)*(x3 - x2);
}
double dis(double x1, double y1, double x2, double y2)//万年不变的两点间距离
{
return sqrt((x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2));
}
Pt point_of_intersection(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4)//求相交直线的交点 比以前的精准了不少 由于这里的原因莫名WA了几次 但是对于重合和部分重合的情况会返回奇怪的结果 因为这时两个三角形面积都是0 然后就除以0了 所幸一般题目不会出现这么极端坑人的数据 顺便一提 这部分知识来自黑书
{
Pt poi;//这个名字不需要解释
double Sabd = xp(x4, y4, x1, y1, x2, y2), Sabc = xp(x3, y3, x1, y1, x2, y2);//分别是两个三角形abd和abc的有向面积的2倍
poi.x = (Sabd*x3 - Sabc*x4) / (Sabd - Sabc);
poi.y = (Sabd*y3 - Sabc*y4) / (Sabd - Sabc);//两个定比分点公式 面积比较好算 因而以面积表示其比例 用减号因为是有向面积 两个面积必然是异号的 没加绝对值是因为最后还得除以面积和 不会对符号产生影响 这些黑书上都有
return poi;
}
double offsetx(double r, int p)//x轴方向的偏移量 参数r是radii 二分的半径 p是惯用临时变量的名称 表示线段的第一个端点在pts数组的下标 至于下面的式子怎么求得 画一条有向线段 标出其可行域方向 正弦余弦值利用端点横纵坐标差分别与线段长度的比值求得 然后将半径r正交分解 就是下面的公式
{
return (pts[p].y - pts[(p + 1) % N].y)*r / d[p];
}
double offsety(double r, int p)//y轴方向的偏移量
{
return (pts[(p + 1) % N].x - pts[p].x)*r / d[p];
}
double fun()
{
for (int p = 0; p < N; p++)
scanf("%lf%lf", &pts[p].x, &pts[p].y);//input
for (int p = 0; p < N; p++)
d[p] = dis(pts[p].x, pts[p].y, pts[(p + 1) % N].x, pts[(p + 1) % N].y);//求出两点间距离 这个值会一直用下去 没有顺手求因为需要两个点
double low = 0, high = 5000, mid;//二分的上下界 以及二分的半径
while (high - low >= 1e-7) {//为了保持和答案完全一致 精确到1e-7 下面检验二分结果
mid = (low + high) / 2;
for (int p = 0; p < N; p++)
ofx[p] = offsetx(mid, p), ofy[p] = offsety(mid, p);//求出两个方向的偏移量 在二分的半径变化前他们一直有效 求出偏移量的目的是为了知道将每个点向两个方向各移动多少才能将整条线段沿着其可行域方向移动这些距离
for (int p = 0; p < N; p++)
pt1[p] = point_of_intersection(pts[p].x + ofx[p], pts[p].y + ofy[p], pts[(p + 1) % N].x + ofx[p], pts[(p + 1) % N].y + ofy[p], pts[(p + 1) % N].x + ofx[(p + 1) % N], pts[(p + 1) % N].y + ofy[(p + 1) % N], pts[(p + 2) % N].x + ofx[(p + 1) % N], pts[(p + 2) % N].y + ofy[(p + 1) % N]);//将偏移量加到点上 算出偏移后的线的交点 作为初始点 交点数目一定和原来多边形上点的数目相同 因为每两条线仍然仅有一个交点 只是位置变化了而已 同一个点在不同线段上时所获得的偏移量也不同
hpt1 = N;//点集1中初始有N个点
for (int p = 0; p < N; p++) {//枚举相邻两个点 做判定线 下面是求半平面交的一般过程
hpt2 = 0;
for (int i = 0; i < hpt1; i++) {//枚举点集1中的点
if (xp(pt1[i].x, pt1[i].y, pts[p].x + ofx[p], pts[p].y + ofy[p], pts[(p + 1) % N].x + ofx[p], pts[(p + 1) % N].y + ofy[p]) <= 0)//如果在半平面的可行域(左侧)或半平面边界上 注意原始点集pts中的点需要带上偏移量 使得他们和点集1的初始点完全一样
pt2[hpt2++] = pt1[i];//点会被加入点集2
else {//否则
if (xp(pt1[(i - 1 + hpt1) % hpt1].x, pt1[(i - 1 + hpt1) % hpt1].y, pts[p].x + ofx[p], pts[p].y + ofy[p], pts[(p + 1) % N].x + ofx[p], pts[(p + 1) % N].y + ofy[p]) < 0)//考虑点集1中前一个点与当前点所成直线与判定线的交点是否在半平面内
pt2[hpt2++] = point_of_intersection(pt1[i].x, pt1[i].y, pt1[(i - 1 + hpt1) % hpt1].x, pt1[(i - 1 + hpt1) % hpt1].y, pts[p].x + ofx[p], pts[p].y + ofy[p], pts[(p + 1) % N].x + ofx[p], pts[(p + 1) % N].y + ofy[p]);//将交点加入点集2
if (xp(pt1[(i + 1) % hpt1].x, pt1[(i + 1) % hpt1].y, pts[p].x + ofx[p], pts[p].y + ofy[p], pts[(p + 1) % N].x + ofx[p], pts[(p + 1) % N].y + ofy[p]) < 0)//考虑后一个点 这样加起来最多可以产生两个新的点 很容易就超出数组大小
pt2[hpt2++] = point_of_intersection(pt1[i].x, pt1[i].y, pt1[(i + 1) % hpt1].x, pt1[(i + 1) % hpt1].y, pts[p].x + ofx[p], pts[p].y + ofy[p], pts[(p + 1) % N].x + ofx[p], pts[(p + 1) % N].y + ofy[p]);
}
}
for (int p = 0; p < hpt2; p++)
pt1[p] = pt2[p];
hpt1 = hpt2;//将点集2赋给点集1
}
if (hpt1)//如果点集1还有点
low = mid;//半径(偏移量)可以继续扩大
else//否则
high = mid;//就要缩小
}
return mid;//最后返回二分的结果
}
int main(void)
{
freopen("vs_cin.txt", "r", stdin);
freopen("vs_cout.txt", "w", stdout);
while (~scanf("%d", &N) && N)//input
printf("%lf\n", fun());//output
}
EOF