 /**//*
題意:?jiǎn)柦o定的n條線把平面分出了幾個(gè)有限的區(qū)域
O(n^2lg(n))
首先處理出所有交點(diǎn),并給所有點(diǎn)標(biāo)號(hào),注意合并相同的點(diǎn)。 map<Point,int> vector<Point>
然后給直接相連的兩點(diǎn)之間連兩條有向邊,處理成鄰接表。 atan2(y,x)得到與x軸的角(極角)
注意只連相鄰點(diǎn)!!!!!!!!!!!!!!
顯然至多有O(n^2)個(gè)點(diǎn)和O(n^2)條邊。
最后算法的核心就是枚舉所有邊作為起始邊,“走”出所有的區(qū)域。
“走”的算法是這樣的,每走到一個(gè)頂點(diǎn),就找從這個(gè)點(diǎn)出發(fā)相對(duì)當(dāng)前邊的方向最“左”的邊。
-- 這樣保證形成的凸多邊形是最小的,最左邊,那么面積直接叉積累加即可
這樣子走,環(huán)的邊只會(huì)走一次!!!

如果最后回到了最初枚舉的起點(diǎn)就找到了一個(gè)有限區(qū)域。
如果這條邊不在當(dāng)前邊的方向的“左”邊說明這不是一個(gè)有限區(qū)域。否則更新當(dāng)前邊繼續(xù)找。
對(duì)于“走”過的邊標(biāo)上標(biāo)記,則以后遇到可以不再繼續(xù)搜下去。
而在找從一個(gè)點(diǎn)出發(fā)相對(duì)當(dāng)前邊的方向最“左”的邊的時(shí)候可以先對(duì)所有邊按極角排序再二分查找。
其實(shí)這個(gè)找的過程在紙上模擬還是非常簡(jiǎn)單的,不過變成代碼就可能有各種bug了……


對(duì)每條邊,朝最左邊的邊走,這樣逆時(shí)針走,叉積面積累加即可
而且,走過的邊只會(huì)走一次,不用再走 這一次要么成環(huán),要么不是環(huán)

跟zoj 1030 類似 往最左/右走 就走出一個(gè)最小的環(huán)
*/
#include<cstdio>
#include<vector>
#include<map>
#include<cmath>
#include<algorithm>

using namespace std;

const double eps = 1e-15;
const double PI = acos(-1.0);
const int MAXN = 81;

struct Point
  {
double x,y;
//map 需要定義 < 、==
bool operator<(const Point &B)const
 {
if(fabs(x-B.x)<eps)return y<B.y;
return x<B.x;
}
bool operator==(const Point &B)const
 {
return fabs(x-B.x)<eps&&fabs(y-B.y)<eps;
}
};
struct Line
  {
Point a,b;
};
bool parallel(const Line &la,const Line &lb)
  {
return fabs((la.a.x-la.b.x)*(lb.a.y-lb.b.y)-(lb.a.x-lb.b.x)*(la.a.y-la.b.y))<eps;
}
Point intersection(const Line &la,const Line &lb)
  {
Point ret = la.a;
double r =
((la.a.x-lb.a.x)*(lb.a.y-lb.b.y)-(la.a.y-lb.a.y)*(lb.a.x-lb.b.x))/
((la.a.x-la.b.x)*(lb.a.y-lb.b.y)-(la.a.y-la.b.y)*(lb.a.x-lb.b.x));
ret.x += (la.b.x-la.a.x)*r;
ret.y += (la.b.y-la.a.y)*r;
return ret;
}
struct Edge
  {
double w;
int v;
bool vi;
 Edge(int v,double w):v(v),w(w),vi(false) {}
bool operator<(const Edge &B)const//按照極角排序
 {
return w<B.w;
}
};

map<Point,int>mp;
vector<Point>vp,p[MAXN];
Line lines[MAXN];
vector<Edge>E[MAXN*MAXN/2];//n*n/2
vector<double> ans;

bool ok(double a,double b)//判斷b是否在a的左邊
  {
if(b>a+eps)return b+eps<a+PI;
return b+eps<a-PI;
}
int search(vector<Edge>&E,double w)//找到w反邊的
  {
double d = w + PI;
if(d>PI+eps)d-=2*PI;
int id;
for(id=0;id<E.size();id++)//w反邊的前一個(gè) 注意角看成循環(huán)的 所以有可能E.size()-1
if(fabs(E[id].w-d)<eps)
 {
if(id)id--;
else id=E.size()-1;
break;
}
//judge again whether id is in the left?
return ok(E[id].w,d)?id:-1;
}
int main()
  {
int T;
for(scanf("%d",&T);T--;T?puts(""):0)
 {
int N;
scanf("%d",&N);
mp.clear();
vp.clear();
for(int i=0;i<N;i++)
 {
scanf("%lf%lf%lf%lf",&lines[i].a.x,&lines[i].a.y,&lines[i].b.x,&lines[i].b.y);
p[i].clear();
for(int j=0;j<i;j++)
 {
if(parallel(lines[i],lines[j]))continue;
Point pt = intersection(lines[i],lines[j]);
mp[pt];//
p[i].push_back(pt);
p[j].push_back(pt);
}
}
//mp用來(lái)處理重復(fù),然后編號(hào) vp存點(diǎn)
int m=0;
for(map<Point,int>::iterator it = mp.begin();it!=mp.end();it++)
 {
vp.push_back(it->first);
it->second = m;
E[m++].clear();
}
//相鄰點(diǎn)連邊
for(int i=0;i<N;i++)
 {
if(p[i].size()<2)continue;
sort(p[i].begin(),p[i].end());
p[i].erase(unique(p[i].begin(),p[i].end()),p[i].end());//unique
double ab = atan2(p[i].back().y-p[i].front().y,p[i].back().x-p[i].front().x);
double ba = atan2(p[i].front().y-p[i].back().y,p[i].front().x-p[i].back().x);
for(int j=1;j<p[i].size();j++)//建邊 注意只連相鄰點(diǎn)!!!!!!!!!!!!!!
 {
int a = mp[p[i][j-1]],b=mp[p[i][j]];
E[a].push_back(Edge(b,ab));
E[b].push_back(Edge(a,ba));
}
}
//對(duì)每個(gè)點(diǎn)的相連的邊按極角排序
for(int i=0;i<m;i++)
sort(E[i].begin(),E[i].end());
ans.clear();
//enum
for(int i=0;i<m;i++)//枚舉每條邊作為起始邊,vi過的就continue
for(int j=0;j<E[i].size();j++)
 {
if(E[i][j].vi)continue;
int a=i,b=j,c;
//按照逆時(shí)針,面積直接叉積累加即可
double S = vp[a].x*vp[E[i][j].v].y-vp[a].y*vp[E[i][j].v].x;
E[i][j].vi=true;
while(E[a][b].v!=i)
 {
//找最左邊的,這樣形成的凸多邊形才最小
//如果相切的,相當(dāng)于有2條邊,正反的,vi不會(huì)影響到反邊
c = search(E[E[a][b].v],E[a][b].w);
if(c==-1)
 {
S=0;break;
}
a=E[a][b].v,b=c;
if(E[a][b].vi)//該邊已vi過,所以不能用了,就S = 0
 {
S=0;break;
}
E[a][b].vi=true;
S += vp[a].x*vp[E[a][b].v].y-vp[a].y*vp[E[a][b].v].x;
}
if(S>2*eps)ans.push_back(S);//S/2>eps
}

printf("%d\n",ans.size());
sort(ans.begin(),ans.end());
for(int i=0;i<ans.size();i++)
printf("%.4f\n",ans[i]/2);//叉積/2
}
return 0;
}


跟zoj 1030 類似 往最左/右走 就走出一個(gè)最小的環(huán)
|
|
常用鏈接
隨筆分類
Links
搜索
最新評(píng)論

|
|