1. 程式人生 > >51Nod-1976-多邊形劃分

51Nod-1976-多邊形劃分

(一)題面:

給一個共有n個點的凸多邊形,求一條將該多邊形劃分為面積和周長都相等的兩部分的直線。
Input
第一行一個正整數n,表示多邊形的點數。(n <= 40000)
接下來的n行,第i+1行,每行兩個實數xi,yi,表示凸多邊形的一個點的座標,點按照逆時針或順時針的順序給出。
其中n,|xi|,|yi|<=40000。
Output
如果存在這樣的直線,將這條直線與凸多邊形的兩個交點的座標分兩行輸出。你所求的直線必須與多邊形有兩個交點,且分多邊形的兩部分周長或面積相差都不能大於10^-3。
如果不存在,輸出"impossible"(不含引號)。
Input示例
4
0 0
3 0
3 3
0 3
Output示例
1 0
2 3

(二)題目大意:

        見題面QAQ.

(三)解題思路:

        像我這麼菜當然是沒想到思路的啦~...

        下面是官方題解:

        ①首先,我們對這條直線的存在性進行證明。
                在凸多邊形上任取一點  P ,定義函式 f(x) :設凸多邊形周長為 C  ,從 P 點出發在凸多邊形上沿順時
         針方向移動 x 個單位長度後,設到達的點為Q1,從P+C/2點出發沿著凸多邊形移動x個單位長度後到達 Q2
        ,函式值為射線 Q1Q2 兩側的面積差。不難發現有 f(x)=−f(x+C2) ,因為此時射線剛好翻轉。根據零點存

        在定理可知在區間 [0,C2]存在一實數 x0 使得 f(x0)=0 ,證畢。

        ②存在性得到證明後,只需求解出函式的一個零點即可。可採用二分法求出零點。顯然零點可能並不一定唯
        一,但我們只需求出一個零點,二分法仍然適用,只需在縮小區間的時候保證區間內一定存在一個零點即可

        (設當前考慮的區間為[l,r],只需保證f(l)與f(r)異號)。至此演算法便水落石出了。

        ③演算法的時間複雜度為 O(nlogC)。

         其圖示如下:

          

        設:F(x)=S1-S2,則F(x+C/2)=S2-S1,又因為F(x)是連續的,所以有了題解中的結論,然後二分求零點即

        可。

(四)具體程式碼:

#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<cmath>
#include<algorithm>
#define id (SP+1)%n
using namespace std;
const int maxn=4e4+10;
struct Point{
    double x,y;
    Point(){}
    Point(double _x,double _y){
        x=_x;y=_y;
    }
}P[maxn];
double dist(Point A,Point B){
    return sqrt((A.x-B.x)*(A.x-B.x)+(A.y-B.y)*(A.y-B.y));
}
double Area(Point A,Point B,Point C){       //三角形面積
    double x1=B.x-A.x,y1=B.y-A.y;
    double x2=C.x-A.x,y2=C.y-A.y;
    return fabs(x1*y2-x2*y1)/2.0;
}
const double esp=1e-9;
Point getp(Point A,Point B,double x){       //求線段AB上距離A點x長度的點
    double d=dist(A,B);
    double ex=(B.x-A.x)/d;
    double ey=(B.y-A.y)/d;
    double _x=A.x+ex*x;
    double _y=A.y+ey*x;
    return Point(_x,_y);
}
double fx(double x,double c,double s,int n){//求F(x)
    int SP=0;
    double d;
    while((d=dist(P[SP],P[id]))<x){x-=d;SP=id;}
    Point p1=getp(P[SP],P[id],x);           //Q1
    double m=c-dist(p1,P[id]),S=0;
    SP=id;
    while((d=dist(P[SP],P[id]))<m){
        S+=Area(p1,P[SP],P[id]);
        m-=d;SP=id;
    }
    Point p2=getp(P[SP],P[id],m);           //Q2
    S+=Area(p1,P[SP],p2);
    return s-S-S;
}
int main(){
    freopen("in.txt","r",stdin);
    int n;
    scanf("%d",&n);
    scanf("%lf%lf%lf%lf",&P[0].x,&P[0].y,&P[1].x,&P[1].y);
    double Len=dist(P[0],P[1]),area=0;
    for(int i=2;i<n;i++){
        scanf("%lf%lf",&P[i].x,&P[i].y);
        Len+=dist(P[i],P[i-1]);
        area+=Area(P[0],P[i-1],P[i]);
    }
    Len+=dist(P[0],P[n-1]);
    double l=0,r=Len/2,c=Len/2,flx=0,fmx=1,mid;
    while(l+esp<r||fabs(fx(mid,c,area,n))<esp){
        mid=(l+r)/2;
        flx=fx(l,c,area,n);
        fmx=fx(mid,c,area,n);
        if(fabs(fmx)<esp)break;
        if(flx*fmx<0)r=mid-esp;
        else         l=mid+esp;
    }
    l=mid;
    int SP=0;
    double d,S=0,len=0;
    while((d=dist(P[SP],P[id]))<l){l-=d;SP=id;}
    Point p1=getp(P[SP],P[id],l);           //Q1
    double m=c-dist(p1,P[id]);
    len+=dist(p1,P[id]);
    SP=id;
    while((d=dist(P[SP],P[id]))<m){
        S+=Area(p1,P[SP],P[id]);
        len+=dist(P[SP],P[id]);
        m-=d;SP=id;
    }
    Point p2=getp(P[SP],P[id],m);           //Q2
    printf("%.12lf %.12lf\n%.12lf %.12lf\n",p1.x,p1.y,p2.x,p2.y);
    return 0;
}

(五)總結一下:

        ①計算幾何往往需要去尋找一些重要的數學關係。

        ②這裡被卡了精度,真是#¥%……&*,我那裡二分判斷的條件貌似不是很“標準”,然後輸出小數的位數

            要夠多,貌似計算幾何問題裡面涉及到精度的問題還不少...。

        ③照著題解的思路寫,程式碼寫得比較難看...有待改進。