題目連結:
http://acm.hdu.edu.cn/showproblem.php?pid=1558
題目類型: 計算集合 , 並查集
題目:
A segment and all segments which are connected with it compose a segment set. The size of a segment set is the number of segments in it. The problem is to find the size of some
segment set.
Sample Input
110P 1.00 1.00 4.00 2.00P 1.00 -2.00 8.00 4.00Q 1P 2.00 3.00 3.00 1.00Q 1Q 3P 1.00 4.00 8.00 2.00Q 2P 3.00 3.00 6.00 -2.00Q 5
Sample Output
12225
題目大意:
輸入幾條線段,線段由座標上的兩點組成, 每一點有x,y,代表x軸和y軸對應的值。
當輸入為Q k時, 輸出與第k條直線直接或間接有相連的線段條數。
分析與總結:
這一題是並查集比較基礎的應用, 但是這題的關鍵在於判斷兩條直線是否相交。
斷兩線段是否相交的方法:
我們分兩步確定兩條線段是否相交:
1. 快速排斥實驗:設以線段 P1P2 為對角線的矩形為R, 設以線段 Q1Q2 為對角線的矩形為T,如果R和T不相交,顯然兩線段不會相交。
2. 跨立實驗:如果兩線段相交,則兩線段必然相互跨立對方。
若P1P2跨立Q1Q2 ,則向量 ( P1 - Q1 ) 和( P2 - Q1 )位於向量( Q2 - Q1 ) 的兩側,即:
(( P1 - Q1 ) × ( Q2 - Q1 )) * (( P2 - Q1 ) × ( Q2 - Q1 )) < 0。(利用了向量叉積)
當 ( P1 - Q1 ) × ( Q2 - Q1 ) = 0 時,說明 ( P1 - Q1 ) 和 ( Q2 - Q1 )共線,但是因為已經通過快速排斥實驗,所以 P1 一定線上段 Q1Q2上;同理,( Q2 - Q1 ) ×(P2 - Q1 ) = 0 說明 P2 一定線上段 Q1Q2上。
所以判斷P1P2跨立Q1Q2的依據是:(( P1 - Q1 ) × ( Q2 - Q1 )) * (( Q2 - Q1 ) × ( P2 - Q1 )) >= 0。
同理判斷Q1Q2跨立P1P2的依據是:(( Q1 - P1 ) × ( P2 - P1 )) * (( P2 - P1 ) × ( Q2 - P1 )) >= 0。
具體情況如所示:(這裡利用了向量叉積來判斷兩個向量是否位於另一向量的兩側)
注意:只有同時滿足以上兩個條件,即相互跨立對方,兩個線段才相交。
代碼:
struct Point{ double x,y;}; double direction(Point p0,Point p1,Point p2){ return (p2.x-p0.x)*(p1.y-p0.y)-(p1.x-p0.x)*(p2.y-p0.y);} bool on_segment(Point p0,Point p1,Point p2){ if((min(p0.x,p1.x)<=p2.x && p2.x<=max(p0.x,p1.x)) && (min(p0.y,p1.y)<=p2.y && p2.y<=max(p0.y,p1.y))) return true; return false;} bool Segment_intersect(Point p1,Point p2,Point p3,Point p4){ double d1,d2,d3,d4; d1 = direction(p3,p4,p1); d2 = direction(p3,p4,p2); d3 = direction(p1,p2,p3); d4 = direction(p1,p2,p4); if(((d1>0 && d2<0)||(d1<0 && d2>0)) && ((d3>0 && d4<0)||(d3<0&&d4>0))) return true; else if(d1==0 && on_segment(p3,p4,p1)) return true; else if(d2==0 && on_segment(p3,p4,p2)) return true; else if(d3==0 && on_segment(p1,p2,p3)) return true; else if(d4==0 && on_segment(p1,p2,p4)) return true; return false;}
知道了怎樣判斷兩線是否相交後, 一切都好辦。
接下來要做的是, 每次添加一條直線是,都進行並查集的Union操作。
如果要知道第k條線所在的集合有幾條線段,只需要判斷並查集中所有以k的跟結點為跟結點的數量有幾個即可
AC代碼:
#include<iostream>#include<cstdio>#include<cmath>#include<cstring>#define N 1005using namespace std;struct Point2D //二維平面點{ double x,y; Point2D():x(0),y(0){} Point2D(double x,double y):x(x),y(y){} double Mod() const {return sqrt(x*x + y*y);} friend Point2D operator-(const Point2D& lh,const Point2D& rh){ return Point2D(lh.x-rh.x, lh.y-rh.y); } friend double operator&(const Point2D& lh,const Point2D& rh){ return lh.x*rh.y - lh.y*rh.x; } friend std::istream& operator>>(std::istream& in, Point2D& pt){ in>>pt.x>>pt.y; return in; }}; struct Segment2D{ Point2D bgn, end; Segment2D():bgn(),end(){} Segment2D(Point2D b,Point2D e):bgn(b),end(e){} Segment2D(double x1,double y1,double x2,double y2):bgn(x1,y1),end(x2,y2){} friend std::istream& operator>>(std::istream& in, Segment2D& pt){ in>>pt.bgn>>pt.end; return in; } friend std::ostream& operator<< (std::ostream& out, Segment2D& pt){ out<<pt.bgn.x<<" "<<pt.bgn.y<<" "<<pt.end.x<<" "<<pt.end.y; return out; }};bool SegmentIntersect(const Segment2D& u, const Segment2D& v){ //1.快速排斥實驗,不相交返回0 if((max(u.bgn.x,u.end.x)>=min(v.bgn.x,v.end.x))&& (max(v.bgn.x,v.end.x)>=min(u.bgn.x,u.end.x))&& (max(u.bgn.y,u.end.y)>=min(v.bgn.y,v.end.y))&& (max(v.bgn.y,v.end.y)>=min(u.bgn.y,u.end.y))); else return false; //2.跨立實驗,u的兩端點在v兩側,並且v的兩端點在u兩側 if((((u.bgn-v.bgn)&(v.end-v.bgn))*((v.end-v.bgn)&(u.end-v.bgn))>=0)&& (((v.bgn-u.bgn)&(u.end-u.bgn))*((u.end-u.bgn)&(v.end-u.bgn))>=0)) return true; else return false;}Segment2D arr[N];int Index, dnum[N];int father[N]; void init(){ for(int i=0; i<N; ++i) father[i]=i, dnum[i] = 1;} int find(int x){ int i, j=x; while(j!=father[j]) j=father[j]; while(x!=j){ i = father[x]; father[x] = j; x = i; } return j;}void Union(int x, int y){ int a = find(x); int b = find(y); if(a!=b) father[a] = b;}int main(){ freopen("input.txt", "r", stdin); int T,n,k,cas=1; char cmd[2]; scanf("%d",&T); while(T--){ scanf("%d", &n); memset(dnum, 0, sizeof(dnum)); Index=0; init(); while(n--){ scanf("%s", cmd); if(cmd[0]=='P'){ cin >> arr[++Index]; for(int i=1; i<=Index-1; ++i){ if(SegmentIntersect(arr[i], arr[Index])) Union(i, Index); } } else if(cmd[0] == 'Q'){ scanf("%d", &k); int x=find(k); int cnt=0; for(int i=1; i<=Index; ++i){ if(x==find(i)) ++cnt; } cout << cnt << endl; } } if(T) printf("\n"); } return 0;}
—— 生命的意義,在於賦予它意義。
原創
http://blog.csdn.net/shuangde800
, By
D_Double