P3829 [SHOI2012] 信用卡凸包 题解
解题思路
不难的题,但是细节有亿点多……
观察三组样例不难发现,我们可以把所有的信用卡的圆角去掉,变成一个长
那么怎么求出凸包周长呢?显然的,我们在求出所有点的坐标后跑一遍 Andrew 或者 Graham 即可。那么现在问题变为如何求出所有点的坐标。
因为题目中已经给出了每张信用卡中心的坐标,那么我们可以很轻松地得到每张信用卡旋转前矩形四个顶点的坐标(其实就是四分之一圆的圆心),那么现在来解决旋转的问题。
我们分别对于每张信用卡进行考虑。我们不妨将中心移到单位圆的圆心上,那么我们根据顶点到中心的
注意事项
- atan2 函数使用时应该把
放前面, 放后面; - 对于所有点按极角排序后一定要去重,不然一个点可能被多次计算;
- 判断两个点是否相等时要加入精度限制,不然去重等于没去;
的值尽量取大一点,不然误差还是挺大的,尤其在乘以一个大的 后。
AC 代码
似乎有点长?
#include<math.h>
#include<stdio.h>
#include<valarray>
#include<stdlib.h>
#include<algorithm>
#define eps 1e-9
#define N 40005
struct Point{
double x,y;
//向量初始化
Point():x(0),y(0){}
Point(
double a,
double b
):x(a),y(b){}
//向量加
inline Point operator +(
const Point &b
) const {
return (Point){x+b.x,y+b.y};
}
//向量减
inline Point operator -(
const Point &b
) const {
return (Point){x-b.x,y-b.y};
}
//向量乘一个数
inline Point operator *(
const double &b
) const {
return (Point){x*b,y*b};
}
//向量除一个数
inline Point operator /(
const double &b
) const {
return (Point){x/b,y/b};
}
//向量点乘
inline double operator ^(
const Point &b
) const {
return x*b.x+y*b.y;
}
//向量叉乘
inline double operator *(
const Point &b
) const {
return x*b.y-y*b.x;
}
inline bool operator ==(
const Point &b
) const {
bool _1=fabs(x-b.x)<eps;
bool _2=fabs(y-b.y)<eps;
return _1&&_2;
}
}a[N],sta[N];
inline double dis(
double x1,double y1,
double x2,double y2
){
double Ox=(x1-x2)*(x1-x2);
double Oy=(y1-y2)*(y1-y2);
return(double)sqrt(Ox+Oy);
}
inline double dis(
const Point &A,
const Point &B
){
double x1=A.x,y1=A.y;
double x2=B.x,y2=B.y;
return dis(x1,y1,x2,y2);
}
inline bool cmp(
Point A,
Point B
){
Point _1=A-a[1];
Point _2=B-a[1];
double d=_1*_2;
if(d>0) return true;
double d1=dis(a[0],A);
double d2=dis(a[0],B);
if(d==0&&d1<d2)
return true;
return false;
}
int m,n,tail;
double A,B,R;
double x,y,d;
inline void Revolve(
double &NowX,
double &NowY,
const double &CentreX,
const double &CentreY,
const double &Angle
){
double r=dis(NowX,NowY,CentreX,CentreY);
double DeltaX=NowX-CentreX;
double DeltaY=NowY-CentreY;
double NowAngle=atan2(DeltaY,DeltaX);
NowAngle+=Angle;
double NowSin=sin(NowAngle);
double NowCos=cos(NowAngle);
NowX=CentreX+(r*NowCos);
NowY=CentreY+(r*NowSin);
}
inline void Turn(
const double &x,
const double &y,
const double &d
){
double x1,x2,x3,x4;
double y1,y2,y3,y4;
x1=x+B*0.5-R,y1=y+A*0.5-R;
x2=x-B*0.5+R,y2=y+A*0.5-R;
x3=x-B*0.5+R,y3=y-A*0.5+R;
x4=x+B*0.5-R,y4=y-A*0.5+R;
Revolve(x1,y1,x,y,d);
Revolve(x2,y2,x,y,d);
Revolve(x3,y3,x,y,d);
Revolve(x4,y4,x,y,d);
a[++n]={x1,y1},a[++n]={x2,y2};
a[++n]={x3,y3},a[++n]={x4,y4};
}
inline void init(){
scanf("%d",&m);
scanf("%lf",&A);
scanf("%lf",&B);
scanf("%lf",&R);
for(
int i=1;
i<=m;++i
){
scanf("%lf",&x);
scanf("%lf",&y);
scanf("%lf",&d);
Turn(x,y,d);
}
for(
int i=2;
i<=n;++i
){
if(a[i].y>a[1].y)
continue;
if(a[i].y==a[1].y
&&a[i].x>=a[1].x)
continue;
std::swap(a[1],a[i]);
}std::sort(a+2,a+1+n,cmp);
n=std::unique(a+1,a+n+1)-a-1;
tail=1;sta[tail]=a[1];
}
double C,ans;
inline void Andrew(){
for(
int i=2;
i<=n;++i
){
while(tail>1){
Point d1=sta[tail]-sta[tail-1];
Point d2=a[i]-sta[tail];
if((d1*d2)>=0) break;--tail;
}sta[++tail]=a[i];
}sta[++tail]=a[1];
for(int i=1;i<tail;++i)
C+=dis(sta[i],sta[i+1]);
}
const double Pi=3.1415926535898;
signed main(){
init();Andrew();
ans=C+(2.0*R*Pi);
printf("%.2lf",ans);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下