【BZOJ1845】[CQOI2005] 三角形面积并(扫描线)
大致题意: 求\(n\)个三角形并的面积。
前言
还以为是什么黑科技,结果一看\(n\le100\)。。。
话说这题精度有点玄学,第二个数据点求出来的答案为\(645.825\),可标准输出是\(645.82\)?害得我只好给答案减个\(eps\)再四舍五入输出。
大致想法
考虑我们把三角形原有的点以及三角形的边两两之间的交点视作关键点,然后定义每一条过关键点且与\(y\)轴平行的直线为关键直线。
经过简单的画图分析,我们就会发现,原本三角形的面积并被这些关键直线分成了许多块(显然),且相邻两条关键直线间的部分必然是若干梯形(否则,如果存在折点,则它必然会作为一个关键点产生一条新的关键直线)。
而梯形的面积计算公式我们都知道:(上底+下底)×高/2。
此处高都是相等的(即相邻关键直线间的距离),而上底+下底其实就是三角形与关键直线的交的并之和。
于是我们只要对每条关键直线枚举每一个三角形计算答案即可。
注意因为可能存在两条直角边分别与\(x\)轴和\(y\)轴平行的直角三角形,所以求三角形与关键直线相交线段要分左右两侧讨论(其实就改了一个\(if\)语句),具体实现详见代码。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100
#define DB long double
#define eps 1e-8
#define Eq(x,y) (fabs((x)-(y))<eps)
using namespace std;
int n,cnt;DB s[10*N*N];
struct P
{
DB x,y;I P(Con DB& a=0,Con DB& b=0):x(a),y(b){}I void read() {scanf("%Lf%Lf",&x,&y);}
I bool operator < (Con P& o) Con {return Eq(x,o.x)?y<o.y:x<o.x;}
}A[N+5],B[N+5],C[N+5];
struct Il
{
DB l,r;I Il(Con DB& a=0,Con DB& b=0):l(min(a,b)),r(max(a,b)){}
I bool operator < (Con Il& o) Con {return Eq(r,o.r)?l>o.l:r<o.r;}
}S[N+5];int k;
I void IP(Con P& A1,Con P& B1,Con P& A2,Con P& B2)//求交点
{
if(Eq(A1.x,B1.x)||Eq(A2.x,B2.x)) return;//此时交点必然与原有点横坐标相同,没必要求
DB k1=(B1.y-A1.y)/(B1.x-A1.x),b1=A1.y-k1*A1.x;
DB k2=(B2.y-A2.y)/(B2.x-A2.x),b2=A2.y-k2*A2.x;if(Eq(k1,k2)) return;//如果无交点
DB p=(b2-b1)/(k1-k2);p>=A1.x&&p<=B1.x&&p>=A2.x&&p<=B2.x&&(s[++cnt]=p,0);//保证交点在线段上
}
I DB F(Con P& A,Con P& B,Con DB& p)
{
DB k=(B.y-A.y)/(B.x-A.x),b=A.y-k*A.x;return k*p+b;//求出线段AB上横坐标为p时的纵坐标
}
I void L(Con DB& p,CI i)//求左侧的交
{
if((Eq(A[i].x,p)&&Eq(B[i].x,p))||p<A[i].x||p>C[i].x) return;//无交
if(Eq(A[i].x,B[i].x)) return (void)(S[++k]=Il(F(A[i],C[i],p),F(B[i],C[i],p)));
if(Eq(B[i].x,C[i].x)) return (void)(S[++k]=Il(F(A[i],B[i],p),F(A[i],C[i],p)));
S[++k]=p<=B[i].x?Il(F(A[i],B[i],p),F(A[i],C[i],p)):Il(F(B[i],C[i],p),F(A[i],C[i],p));
}
I void R(Con DB& p,CI i)//求右侧的交
{
if((Eq(B[i].x,p)&&Eq(C[i].x,p))||p<A[i].x||p>C[i].x) return;
//注意除此if语句第一个条件外,其他部分都与上面的函数相同
if(Eq(A[i].x,B[i].x)) return (void)(S[++k]=Il(F(A[i],C[i],p),F(B[i],C[i],p)));
if(Eq(B[i].x,C[i].x)) return (void)(S[++k]=Il(F(A[i],B[i],p),F(A[i],C[i],p)));
S[++k]=p<=B[i].x?Il(F(A[i],B[i],p),F(A[i],C[i],p)):Il(F(B[i],C[i],p),F(A[i],C[i],p));
}
I DB Merge()//求并
{
RI i,T=0;for(sort(S+1,S+k+1),i=1;i<=k;++i)//排序后枚举每一段
{W(T&&S[T].r>S[i].l) S[i].l>S[T].l&&(S[i].l=S[T].l),--T;S[++T]=S[i];}//类似于单调栈的过程
DB t=0;for(i=1;i<=T;++i) t+=S[i].r-S[i].l;return t;//统计和,返回答案
}
int main()
{
RI i,j;for(scanf("%d",&n),i=1;i<=n;++i) A[i].read(),B[i].read(),C[i].read(),
s[++cnt]=A[i].x,s[++cnt]=B[i].x,s[++cnt]=C[i].x,C[i]<B[i]&&(swap(B[i],C[i]),0),
C[i]<A[i]&&(swap(A[i],C[i]),0),B[i]<A[i]&&(swap(A[i],B[i]),0);
for(i=1;i<=n;++i) for(j=i+1;j<=n;++j)//枚举线段求交点
IP(A[i],B[i],A[j],B[j]),IP(A[i],B[i],A[j],C[j]),IP(A[i],B[i],B[j],C[j]),
IP(A[i],C[i],A[j],B[j]),IP(A[i],C[i],A[j],C[j]),IP(A[i],C[i],B[j],C[j]),
IP(B[i],C[i],A[j],B[j]),IP(B[i],C[i],A[j],C[j]),IP(B[i],C[i],B[j],C[j]);
sort(s+1,s+cnt+1),cnt=unique(s+1,s+cnt+1)-s-1;//排序去重
DB t,ans=0;for(i=2;i<=cnt;++i)//枚举关键直线的间隔
{
for(k=0,j=1;j<=n;++j) R(s[i-1],j);t=Merge();//求左侧关键直线右侧贡献
for(k=0,j=1;j<=n;++j) L(s[i],j);ans+=(s[i]-s[i-1])*(t+Merge())/2;//求右侧关键直线左侧贡献,用梯形公式计算答案
}return printf("%.2Lf",ans-eps),0;//玄学精度
}
待到再迷茫时回头望,所有脚印会发出光芒