[SHTSC 2012] 信用卡凸包

可能是因为太无聊了吧!可能是因为马上就要退役了吧!可能是因为……
反正我就是来写题解了,还能说什么好?数学确实很重要啊!

题目大概意思如下:
首先一张信用卡大体框架是矩形,但是在四个边缘进行了圆滑处理(四个角是半径为r的1/4圆,并且圆满足与矩形的边相切),如下图




现在告诉你信用卡的规格(高、宽、1/4圆半径)还有信用卡张数,并且告诉你每张信用卡在桌面上的坐标以及旋转角度(弧度制,我们默认最开始时信用卡正放于桌面),让你求包围所有信用卡的凸包周长。

输入的第一行是一个正整数 n,表示信用卡的张数。 第二行包含三个实数 a, b, r ,分别表示信用卡(圆滑处理前)竖直方向的长度、水平方向的长度,以及1/4圆的半径。 之后n行,每行包含三个实数 x ,y, θ,分别表示一张信用卡中心(即对角线交点)的横、纵坐标,以及绕中心逆时针旋转的弧度。 
 输出只有一行,包含一个实数,表示凸包的周长,四舍五入精确到小数点后2位。


这题不给样例不行:





我相信“凸包”这个词的出现已经有相当大的指导意义了,计算几何很明显,此题坑点不在凸包上,而在如何处理这些神奇的圆弧长度。
开始看着题果断神犇题,感觉好神奇,是不是原来的凸包算法都不能用了?不过很显然,不是的。
其实细心一点思考,应该把图形变一下(以样例3为例,作图略坑,要怪只能怪windows画图太垃圾>_<......)



很容易发现对顶的黑色夹角与蓝色夹角互补,证明略,而且凸包直线部分和内围直线部分凸包等长,实际上问题转化为求所有圆弧长度,这个只要知道内围凸包上每三个顶点对应的角度就好办了,内围凸包是模板算法,每一个信用卡对应四个点,然后各种三角函数乱搞就可以了,反正我表示三角函数烂成渣,还好比较happy一次AC。




#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#define SetPoint(_x,_y) (a[++tot].x=_x,a[tot].y=_y)
#define MaxN 10010
using namespace std;
const double pi=acos(-1);
double w,h,r;
int st[MaxN];
int n,tot,top;
double sqr(double x)
{
	return x*x;
}
struct point
{
	double x,y;
}a[4*MaxN];
#define po const point &
inline double xmul(po a,po b,po c)
{
	return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);
}
inline double dist(po a,po b)
{
	return sqrt(sqr(a.x-b.x)+sqr(a.y-b.y));
}
inline bool cmp(po p,po q)
{
	double d=xmul(a[1],p,q);
	return d==0.0 ? dist(a[1],p)<dist(a[1],q):d>0;
}
inline void init()
{
	double x,y,R,angle,base,_x,_y;
	cin>>n>>h>>w>>r;
	h/=2,w/=2,h-=r,w-=r;
	base=asin(h/sqrt(sqr(w)+sqr(h)));
	R=sqrt(sqr(h)+sqr(w));
	for(int i=1;i<=n;i++)
	{
		scanf("%lf%lf%lf",&x,&y,&angle);
		_x=R*cos(angle+base);
		_y=R*sin(angle+base);
		SetPoint(x+_x,y+_y);
		SetPoint(x-_x,y-_y);
		_x=R*cos(angle-base);
		_y=R*sin(angle-base);
		SetPoint(x+_x,y+_y);
		SetPoint(x-_x,y-_y);			
	}
}
inline double GetRoundPartAngle(po a,po b,po c)
{
	double mx=a.x-b.x,my=a.y-b.y;
	double nx=c.x-b.x,ny=c.y-b.y;
	if(mx*ny-nx*my==0.0) return 0.0;
	double ans=acos((mx*nx+my*ny)/(dist(a,b)*dist(c,b)));
	return pi-ans;
}
inline void work()
{
	int pos=1;
	for(int i=2;i<=tot;i++)
		if(a[pos].y>a[i].y)
			pos=i;
		else if(a[pos].y==a[i].y)
			if(a[pos].x>a[i].x)
				pos=i;
	swap(a[1],a[pos]);
	sort(a+2,a+tot+1,cmp);
	st[++top]=1;
	st[++top]=2;
	for(int i=3;i<=tot;st[++top]=i++)
		while(top>2&&xmul(a[st[top-1]],a[st[top]],a[i])<=0)
			top--;
	double ans=dist(a[1],a[st[top]]);
	for(int i=1;i<top;i++)
		ans+=dist(a[st[i]],a[st[i+1]]);
	ans+=r*GetRoundPartAngle(a[st[top]],a[1],a[2]);
	ans+=r*GetRoundPartAngle(a[st[top-1]],a[st[top]],a[1]);
	for(int i=2;i<top;i++)
		ans+=r*GetRoundPartAngle(a[st[i-1]],a[st[i]],a[st[i+1]]);
	printf("%.2lf\n",ans);
}
int main()
{
	freopen("card.in","r",stdin);
	freopen("card.out","w",stdout);
	init();
	work();
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值