P2116 城墙 题解
Update on 2022.8.12:将代码中的 dis 函数换成了 std::hypot,l*6.2831853 换成了 std::numbers::pi*l*2(需要 <numbers> 头文件且需要 C++20)
前置知识:二维凸包
二维凸包模板传送门
OI Wiki
凸包的定义:在平面上能包含所有给定点的最小凸多边形叫做凸包。
求凸包的算法一般有两种,分别是 Andrew 算法和 Graham 算法,这里只介绍 Graham 算法。
Graham 算法的原理就是维护一个点集,通过扫描所有点向点集中不断添加更优的点和删除“凹”点最终形成凸包(因为如果一个凹多边形符合要求,那么必有一个凸多边形在周长上优于它,不然为什么不叫凹包)。
算法步骤
首先需要对点集排序。
Graham 算法快的一个很重要的原因就是因为预先进行了排序,所以可以减少扫描次数。
我们选择一个
我们按照排序的顺序,依次连接每一个点,如果发现当前点
下面是图解:
首先连接
连接
连接
踢出
回到本题。
P2116 城墙
题目要求最近距离不小于
按照题意画出图,可以发现要求的就是多边形外面围的这一圈,由若干圆弧和线段组成。
易得
怎么求多边形周长?可以用凸包求解,将给定点丢进凸包板子里就行了。
至于圆弧的总长度,猜想它就是一个圆的周长,也就是
证明:易得在
同理,每个圆弧所对的圆心角都与多边形的一个内角互补。
因为凸多边形的外角和为
最终答案即为凸包周长
双倍经验 UVA1303 Wall (注意细节,有多组数据,而且要输出两个换行,而且最后一组数据要少输出一个换行)
代码
#include<iostream>
#include<algorithm>
#include<numbers>
#include<cmath>
using namespace std;
int n,l,t=1;
double ans;
struct point
{
double x,y;
}p[1005],s[1005];
double check(point a1,point a2,point b1,point b2)
{
return (a1.x-a2.x)*(b1.y-b2.y)-(a1.y-a2.y)*(b1.x-b2.x);
}
bool cmp(point a,point b)
{
double k=check(a,p[1],b,p[1]);
if(k>0||(k==0&&hypot(a.x-p[1].x,a.y-p[1].y)<hypot(b.x-p[1].x,b.y-p[1].y))) return 1;
return 0;
}
int main()
{
cin>>n>>l;
for(int i=1;i<=n;i++)
{
cin>>p[i].x>>p[i].y;
if(p[i].y<p[1].y) swap(p[i],p[1]);
}
sort(p+2,p+n+1,cmp);
s[1]=p[1];
for(int i=2;i<=n;i++)
{
if(p[i-1].x==p[i].x&&p[i-1].y==p[i].y) continue;
while(t>1&&check(s[t],s[t-1],p[i],s[t])<0) t--;
s[++t]=p[i];
}
s[t+1]=p[1];
for(int i=1;i<=t;i++) ans+=hypot(s[i].x-s[i+1].x,s[i].y-s[i+1].y);
cout<<round(ans+numbers::pi*l*2);
return 0;
}