/*
    題意:?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)