【计算几何】【扫描法】The Sky is the Limit

针对广告公司需求,本文介绍了一种使用扫描法计算由多个二维三角形组成的天际线总长度的方法。通过处理所有分割点并进行排序,从左至右扫描以累加最高山峰的边长,有效避免了复杂的分类讨论。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 传送门: http://lx.lanqiao.cn/problem.page?gpid=T483
问题描述
  Banff城雇用了一家广告公司来提升这座城市对潜在的游客的吸引力。其中一个计划中的口号声称延伸在这座城市周围的山脉组成了加拿大最美丽的天际线。但是加拿大消费者保护协会认为“最美丽的天际线”是一种主观的,无法证实的声称,而且可能因此让人误解。
  然后那个广告公司就想出了一个口号“Banff——加拿大最长的天际线”。虽然没有那么引人注意,但这是有希望能证实的,而且这样就能被加拿大复杂的广告法律所接受了。
  这就是你要介入的原因。广告公司需要的是一个能确定天际线的长度的程序。把每座山脉看作是一个二维的上面两条边长度相等的三角形。一条天际线是一座或多座山脉的轮廓。天际线的长度就是轮廓的总长度。下面的左图显示了三座山脉。右图显示了(用黑线)天际线和(用虚线)山脉上面的边中不是天际线的部分。注意位于山脉之间的地平线部分不在天际线考虑的范围内。
在这里插入图片描述
输入格式
  第一行包含一个正整数N,表示范围内的山脉数。接下来N行每行用三个整数X,H,B来描述每一座山脉,分别表示山脉的最高点到某个固定的点的水平距离,山脉的垂直高度和山脉的底边的宽度。每座山脉的底部在同一条水平线上。数据满足N≤100,H>0而且B>0。
输出格式
  输出只有一行,即天际线的长度。输出那个长度四舍五入后的结果。
样例输入
3
20 30 35
37 24 29
60 20 13
样例输出
138
数据规模和约定
  对于20%的数据,1≤N≤5
  对于50%的数据,1≤N≤30
  对于100%的数据,1≤N≤100


 因为方法比较经典,所以记录一下。采用的是“扫描法”,也就是首先处理出所有的分割点,分割点包括山峰的三端点,以及两两山峰之间的交点。所有的分割点进行排序,排序完毕后从左到右扫描,答案累加上相邻分割点之间最高山峰的边长。这么做避免了复杂的分类讨论。类似的,如果需要求很多个圆交或者并的周长,也可以采用类似的扫描方法。
 写线段相交还需要熟练,另外,浮点误差需要小心,一不小心容易出错(比如在求区间中最高山峰时)。

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

typedef long double db;
typedef complex<db> Point;
typedef Point Vector;

int n,a[105],b[105],c[105],cn;
db cut[25005],rx,ans;

db Cross(Vector A, Vector B)
{
	return imag(conj(A)*B);
}

bool get_Cross(db px, db py, db qx, db qy, db p2x, db p2y, db q2x, db q2y)
{
	Point P=(Point){px,py};
	Vector v=(Vector){qx-px,qy-py};
	Point Q=(Point){p2x,p2y};
	Vector w=(Vector){q2x-p2x,q2y-p2y};
	Vector u=P-Q;
	if(Cross(v,w)==0)
		return false;
	db t=Cross(w,u)/Cross(v,w);
	rx=(P+v*t).real();
	if(rx<min(px,qx)||rx>max(px,qx))
		return false;
	if(rx<min(p2x,q2x)||rx>max(p2x,q2x))
		return false;
	return true;
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d%d",&a[i],&b[i],&c[i]);
		cut[++cn]=a[i]-c[i]/2.0,cut[++cn]=a[i]+c[i]/2.0,cut[++cn]=a[i];
	}
	for(int i=1;i<n;i++)
		for(int j=i+1;j<=n;j++)
		{
			db o=a[i]-c[i]/2.0,p=a[i]+c[i]/2.0,q=a[j]-c[j]/2.0,r=a[j]+c[j]/2.0;
			if(get_Cross(o,0,a[i],b[i],q,0,a[j],b[j]))
				cut[++cn]=rx;
			if(get_Cross(o,0,a[i],b[i],a[j],b[j],r,0))
				cut[++cn]=rx;
			if(get_Cross(a[i],b[i],p,0,q,0,a[j],b[j]))
				cut[++cn]=rx;
			if(get_Cross(a[i],b[i],p,0,a[j],b[j],r,0))
				cut[++cn]=rx;
		}
	sort(cut+1,cut+cn+1);
	cn=unique(cut+1,cut+cn+1)-cut-1;
	for(int i=1,pos;i<cn;i++)
	{
		pos=0;
		db tmpl=-1E9,tmpr=-1E9,tl,tr;
		for(int j=1;j<=n;j++)
			if(cut[i]>=a[j]-c[j]/2.0&&cut[i+1]<=a[j])
			{
				tl=(cut[i]-a[j]+c[j]/2.0)/(c[j]/2.0)*b[j];
				tr=(cut[i+1]-a[j]+c[j]/2.0)/(c[j]/2.0)*b[j];
				if(tl+tr>tmpl+tmpr)   //就是这个地方!不要写tl>tmpl||tr>tmpr,因为浮点误差的原因,有可能实际相同的点,反而低山峰的那个点更高那么一小丢丢,造成pos指向了低山峰。
					tmpl=tl,tmpr=tr,pos=j;
			}
			else if(cut[i]>=a[j]&&cut[i+1]<=a[j]+c[j]/2.0)
			{
				tl=b[j]-(cut[i]-a[j])/(c[j]/2.0)*b[j];
				tr=b[j]-(cut[i+1]-a[j])/(c[j]/2.0)*b[j];
				if(tl+tr>tmpl+tmpr)
					tmpl=tl,tmpr=tr,pos=j;
			}
		if(pos)
			ans+=(cut[i+1]-cut[i])*sqrtl(1+(b[pos]/(c[pos]/2.0))*(b[pos]/(c[pos]/2.0)));
	}
	printf("%d",(int)(ans+0.5));
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值