1. 程式人生 > >poj 2284 That Nice Euler Circuit (常用線段 (直線)之類模板)

poj 2284 That Nice Euler Circuit (常用線段 (直線)之類模板)

題目連結:poj 2284

參考:劉汝佳演算法入門經典

 

題意:平面上有一個包含n個端點的一筆畫,第n個端點總是和第一個端點重合,因此團史一條閉合曲線。組成一筆畫的線段可以相交,但是不會部分重疊。求這些線段將平面分成多少部分(包括封閉區域和無限大區域)。

分析:若是直接找出所有區域,或非常麻煩,而且容易出錯。但用尤拉定理可以將問題進行轉化,使解法變容易。

尤拉定理:設平面圖的頂點數、邊數和麵數分別為V,E,F,則V+F-E=2

這樣,只需求出頂點數V和邊數E,就可以求出F=E+2-V

設平面圖的結點由兩部分組成,即原來的結點和新增的結點。由於可能出現三線共點,需要刪除重複的點。

 

程式碼可以作為以後的模板:總結一下,模板要高度可靠,不能出一丁點問題。

1,線段與線段相交:首先先通過快速排斥實驗,接著判斷兩次跨立實驗

2,直線與線段直線與直線:直接判斷兩次跨立實驗

 

#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;

const int N = 300 + 5;

struct point {
    double x, y;
    point(double x = 0, double y = 0) : x(x), y(y) { }
};

typedef point point;

point operator + (point A, point B) { return point(A.x + B.x, A.y + B.y); }
point operator - (point A, point B) { return point(A.x - B.x, A.y - B.y); }
point operator * (point A, double p) { return point(A.x * p, A.y * p); }
point operator / (point A, double p) { return point(A.x/p, A.y/p); }

bool operator < (const point& a, const point& b) {
    return a.x < b.x || (a.x == b.x && a.y < b.y);
}

const double eps = 1e-10;
int dcmp(double x) {
    if(fabs(x) < eps) return 0;
    else return x < 0 ? -1 : 1;
}

bool operator == (const point& a, const point& b) {
    return dcmp(a.x - b.x) == 0 && dcmp(a.y - b.y) == 0;
}

double Dot(point A, point B) { return A.x * B.x + A.y * B.y; } ///點積
double Cross(point A, point B) { return A.x * B.y - A.y * B.x; } ///叉積

point Getlinenode(point P, point v, point Q, point w) { ///兩直線交點(點,向量,點,向量)
    point u = P - Q;
    double t = Cross(w, u) / Cross(v, w);
    return P + v * t;
}

///這是劉汝佳書上的
//bool isCross(point a1, point a2, point b1, point b2) { ///判斷兩線段是否相交(不包括端點)
//
//    ///第一步,快速排斥實驗
//    if(!(min(s1.x,e1.x)<=max(s2.x,e2.x)&&min(s2.x,e2.x)<=max(s1.x,e1.x)&&
//       min(s1.y,e1.y)<=max(s2.y,e2.y)&&min(s2.y,e2.y)<=max(s1.y,e1.y))) return false;
//
//    double c1 = Cross(a2-a1, b1-a1), c2 = Cross(a2-a1, b2-a1),
//           c3 = Cross(b2-b1, a1-b1), c4 = Cross(b2-b1, a2-b1);
//    return dcmp(c1) * dcmp(c2) < 0 && dcmp(c3) * dcmp(c4) < 0;
//}

///這是自己的癖好
bool isCross(point s1,point e1,point s2,point e2)///判斷兩線段是否相交(不包括端點)
{
    ///第一步,快速排斥實驗
    if(!(min(s1.x,e1.x)<=max(s2.x,e2.x)&&min(s2.x,e2.x)<=max(s1.x,e1.x)&&
       min(s1.y,e1.y)<=max(s2.y,e2.y)&&min(s2.y,e2.y)<=max(s1.y,e1.y))) return false;

    double c1=Cross(s2-s1,e1-s1),c2=Cross(e1-s1,e2-s1);
    double c3=Cross(s1-s2,e2-s2),c4=Cross(e2-s2,e1-s2);

    if(dcmp(c1*c2)>0&&dcmp(c3*c4)>0) return 1;
    return 0;
}

bool OnSegment(point p, point a1, point a2) { ///判斷點是否線上段上(不包括端點)
    return dcmp(Cross(a1-p, a2-p)) == 0 && dcmp(Dot(a1-p, a2-p)) < 0;
}
point P[N], V[N*N];

int main()
{
    int n, cas = 0;
    while(~scanf("%d",&n) && n) {
        for(int i = 0; i < n; i++) {
            scanf("%lf%lf", &P[i].x, &P[i].y);
            V[i] = P[i];
        }
        n--;
        int vcnt = n, ecnt = n;
        for(int i = 0; i < n; i++)
            for(int j = i + 1; j < n; j++) {
                if(isCross(P[i], P[i+1], P[j], P[j+1])) ///窮舉法看兩兩線段是否相交
                    V[vcnt++] = Getlinenode(P[i], P[i+1]-P[i], P[j], P[j+1]-P[j]);
                ///保留因為線段相交產生的新節點
            }

        sort(V, V+vcnt);
        vcnt = unique(V, V+vcnt) - V;
        ///unique去除相鄰的重複的頂點,但實際上是將相同的數移到了陣列的後面
        for(int i = 0; i < vcnt; i++)
            for(int j = 0; j < n; j++)
                if(OnSegment(V[i], P[j], P[j+1])) ///判斷點在不線上段中(不包括端點),每交一次,邊多一條
                    ecnt++;
        int ans = ecnt + 2 - vcnt;
        printf("Case %d: There are %d pieces.\n", ++cas, ans);
    }
    return 0;
}