这道题看到名字应该就发现有多“难”了,我做完之后只好这样评价“计算几何 == WA很久 + dcmp + EPS + 这TM就一水题!”。
自认为代码没问题的看客们可以先给答案加上一个EPS再提交一遍试试,如果AC了请看本文最后一个部分。
本文的各部分请根据需求选择阅读,不喜勿喷,欢迎提出文中错误。
一、解决本题的基础知识
1.1 叉积的基础知识
学过数学的都知道有个叫向量(vector)的东西,不过这个不是STL里面的那个vector模板,而是几何概念。
为了方便表示,我们把所有的点都看做从原点到该点的向量,这样点之间的减法也有了几何意义。
我们都知道向量点积的定义:(x1,y1) · (x2,y1) == x1 * x2 + y1 * y2,显然满足交换律;代数意义是模之积乘上cos<v1,v2>(v1,v2表示向量),几何意义是一向量对另一向量的投影与其模的的乘积;据此我们可以判断两线段(直线)是否平行(共线)或者垂直,其他的都会出现两种位置关系,这时我们就可以用到叉积了!
叉积的定义:(x1,y1) × (x2,y2) == x1 * y2 - x2 * y1,显然满足反交换律;当然,从公式中是看不出什么的(除非你作死去证明),所以用好结论就够了:令t = v1 × v2,若t > 0则v1在v2的顺时针
180°范围内,若t < 0则v1在v2的逆时针180°范围内,若t == 0则v1与v2同向或反向。(如图)
1.2 两线段判交
在平面上,两直线只有两种关系,平行(parallel)和相交(intersect),在判断两线段的位置关系时,若这两条线段所在的直线相互平行,它们显然不会相交;
由上图,我们可以得到两线段P1P2和Q1Q2相交的必要条件:(P2 - P1) × (Q1 - P1) * (P2 - P1) × (Q2 - P1) <= 0;
然而这或许还不足以判断它们相交,如下图:
我们发现,在左图中只满足了一个“跨立”条件,即Q1和Q2在直线P1P2的两侧,于是我们应该做两次跨立实验才能保证它们是相交的;而右图则是边界条件的特殊情况,不方便用跨立实验来判断,不过通过观察两张图后,我们发现:当两线段不相交时,以这两条线段为对角线的格点矩形没有交集!于是我们可以借助这一点来增加判交条件以保证算法的正确性,我们称之为快速排斥实验,如下图:
(这张图网上好像到处都是-_-||)
1.3 求交点
首先它们得相交=。=;
可以通过叉积得到的有向面积的比值来判断交点到某一端点的向量,然后加上那个端点就得到交点坐标了。
(懒得画图了= =)
二、AC代码
1 #include<cstdio> 2 #include<iostream> 3 #include<algorithm> 4 #include<ctime> 5 #include<cstring> 6 #include<string> 7 #include<queue> 8 #include<cmath> 9 10 #define file(f) freopen(f".in","r",stdin); freopen(f".out","w",stdout) 11 12 #define min(a,b) ((a)<(b)? (a):(b)) 13 #define max(a,b) ((a)>(b)? (a):(b)) 14 15 using namespace std; 16 17 const double EPS = 1e-8; 18 const double INF = 10000 + 10; 19 20 int dcmp(double a){ 21 if(fabs(a) < EPS) return 0; 22 else return a < 0 ? -1 : 1; 23 } 24 25 struct R{ 26 double dat; 27 void r(){ 28 scanf("%lf",&dat); 29 } 30 void w(){ 31 printf("%.2lf\n",dat); 32 } 33 bool operator == (const R c){ 34 return !dcmp(dat - c.dat);//dat-EPS < c.dat && dat+EPS > c.dat; 35 } 36 bool operator < (const R c){ 37 return dcmp(dat - c.dat) == -1; 38 } 39 bool operator > (const R c){ 40 return dcmp(dat - c.dat) == 1; 41 } 42 bool operator >= (const R c){ 43 return (*this).operator==(c) || (*this).operator>(c); 44 } 45 bool operator <= (const R c){ 46 return (*this).operator==(c) || (*this).operator<(c); 47 } 48 R operator + (const R c){ 49 R a; 50 a.dat = dat + c.dat; 51 return a; 52 } 53 R operator - (const R c){ 54 R a; 55 a.dat = dat - c.dat; 56 return a; 57 } 58 R operator * (const R c) const{ 59 R a; 60 a.dat = dat * c.dat; 61 return a; 62 } 63 R operator / (const R c){ 64 R a; 65 a.dat = dat / c.dat; 66 return a; 67 } 68 }; 69 R zero,two,minu;//zero == (double)0,two == (double)2,minu == (double)-1; 70 71 struct P{ 72 R x,y; 73 void r(){ 74 x.r(),y.r(); 75 } 76 P operator - (const P c){ 77 P a; 78 a.x = x - c.x,a.y = y - c.y; 79 return a; 80 } 81 P operator + (const P c){ 82 P a; 83 a.x = x + c.x,a.y = y + c.y; 84 return a; 85 } 86 P operator * (const R c){ 87 P a; 88 a.x = x * c,a.y = y * c; 89 return a; 90 } 91 P operator / (const R c){ 92 P a; 93 a.x = x / c,a.y = y / c; 94 return a; 95 } 96 bool operator < (const P c){ 97 return mo() < c.mo(); 98 } 99 bool operator > (const P c){ 100 return mo() > c.mo(); 101 } 102 R CP(P c){ 103 return (x*c.y) - (y*c.x); 104 } 105 R DP(P c){ 106 return (x*c.x) + (y*c.y); 107 } 108 R mo() const{ 109 R ret; 110 ret.dat = sqrt((x.dat * x.dat) + (y.dat * y.dat)); 111 return ret; 112 } 113 }; 114 115 struct L{ 116 P f,t;//from,to 117 R k; 118 bool p; 119 void getk(){ 120 P v = f+t; 121 if(t.mo() == zero) k = zero; 122 else if(v.x == f.x){ 123 p = 1; 124 k.dat = INF; 125 } 126 else{ 127 p = 0; 128 k = (v.y-f.y) / (v.x-f.x); 129 } 130 } 131 void r(){ 132 P to; 133 f.r(),to.r(); 134 if(to.y < f.y) swap(f,to); 135 t = to - f; 136 getk(); 137 } 138 bool cr(L c){//判交 139 P A1 = f,A2 = f + t,B1 = c.f,B2 = c.f + c.t; 140 if(!(min(A1.x,A2.x) <= max(B1.x,B2.x) && 141 min(B1.x,B2.x) <= max(A1.x,A2.x) && 142 min(A1.y,A2.y) <= max(B1.y,B2.y) && 143 min(B1.y,B2.y) <= max(A1.y,A2.y) ))//快速排斥实验 144 return false; 145 if((A1-B1).CP(A2-B1) * (A1-B2).CP(A2-B2) > zero || 146 (B1-A1).CP(B2-A1) * (B1-A2).CP(B2-A2) > zero)//跨立实验 147 return false; 148 else 149 return true; 150 } 151 P node(L c){//求交点 152 P A1 = f,A2 = f + t,B1 = c.f,B2 = c.f + c.t; 153 R p = (B1-A1).CP(B2-A1) / ((A1-B2).CP(A2-B2) - (A1-B1).CP(A2-B1)); 154 return (t * p) + f; 155 } 156 }; 157 L X,Y; 158 159 void init(L &l1,L &l2); 160 void work(L l1,L l2); 161 R work2(L l1,L l2); 162 R work3(L l1,L l2); 163 164 int main(){ 165 #ifndef ONLINE_JUDGE 166 file("2826"); 167 #endif 168 L l1,l2; 169 int T; 170 scanf("%d",&T); 171 while(T --){ 172 init(l1,l2); 173 work(l1,l2); 174 } 175 return 0; 176 } 177 178 void init(L &l1,L &l2){ 179 l1.r(); 180 l2.r(); 181 zero.dat = 0.00; 182 two.dat = 2.00; 183 minu.dat = -1.00; 184 Y.t.y.dat = INF,Y.t.x.dat = 0; 185 } 186 187 void work(L l1,L l2){ 188 R ans; 189 if(l1.k == zero || l2.k == zero || (!l1.cr(l2)))//有一条线段平行于x轴或互不相交 190 ans = zero; 191 else if(l1.p || l2.p)//有一条线段平行于y轴(已经排除了平行和不相交) 192 ans = work2(l1,l2); 193 else//还剩三种一般情况:同时左(右)斜或一左一右 194 ans = work3(l1,l2); 195 ans.w(); 196 } 197 198 R work2(L l1,L l2){ 199 if(l2.p) swap(l1,l2);//l1为平行于y轴的线段 200 P N = l1.node(l2),v1,v2; 201 v1 = l2.f + l2.t - N; 202 v2 = l1.f + l1.t - N; 203 if(v1.mo() == zero || v2.mo() == zero) return zero; 204 R cos = v1.DP(v2) / (v1.mo() * v2.mo()); 205 P h = (Y.t / Y.t.mo()) * min((v1 * cos).mo(),v2.mo()); 206 v1 = (v1/v1.mo()) * (h / cos).mo(),v2 = h; 207 return max(v1.CP(v2),v1.CP(v2) * minu) / two; 208 } 209 210 R work3(L l1,L l2){ 211 P N = l1.node(l2); 212 if(abs(l1.k.dat) < abs(l2.k.dat)) swap(l1,l2);//l1相比l2的更靠近y轴 213 P v1,v2; 214 L l3 = Y; 215 l3.f = N; 216 v1 = l1.f + l1.t - N; 217 v2 = l2.f + l2.t - N; 218 if(v1.mo() == zero || v2.mo() == zero) return zero; 219 R cos1,cos2; 220 cos1 = v1.DP(l3.t) / (v1.mo() * l3.t.mo()); 221 cos2 = v2.DP(l3.t) / (v2.mo() * l3.t.mo()); 222 P h1,h2; 223 h1 = (Y.t / Y.t.mo()) * (v1.mo() * cos1); 224 h2 = (Y.t / Y.t.mo()) * (v2.mo() * cos2); 225 if((v1-h1).mo() >= (v2-h2).mo() && l1.k * l2.k > zero) return zero; 226 else{ 227 l1.t = (l1.t / l1.t.mo()) * min(v1.mo(),(v2.mo() * cos2) / cos1); 228 l2.t = (l2.t / l2.t.mo()) * min(v2.mo(),(v1.mo() * cos1) / cos2); 229 l3.t = (l3.t / l3.t.mo()) * l1.t.mo() * cos1; 230 R r1 = l3.t.CP(l1.t) / two,r2 = l3.t.CP(l2.t) / two,ret; 231 ret = r1 - r2; 232 return max(ret,ret * minu);//即abs(ret) 233 } 234 }
三、爬坑指南
1、记住dcmp(三态函数)的作用,做double类型答案的题都要注意精度误差;
2、EPS的选择通常为1e-6,1e-8,1e-12,1e-19,根据题意和需要选取;
3、有时测评机开了严格比较时,答案为-0.00是无法通过的,这时给答案加上一个EPS就变成(+)0.00了(这也是开头那句话的原理)。
四、后记
这只是一道非常简单的计算几何题,但是还是有很多东西需要注意和总结,希望本文对看过的朋友们有所帮助。