您的位置:首页 > 其它

凸包求法 POJ 1113 Wall 凸包

2017-03-26 22:43 369 查看
题目大意:

有个愚蠢的皇帝要你造墙将城堡围起来,城堡的顶点有N个,墙必须离城堡的边至少L单位远,并且墙的总长度尽量小。求此长度?



因为墙的长度要尽量小,所以墙不能凹进去,一定是一个凸多边形。如图,最终的墙类似虚线部分,由凸包的周长和一个半径L的圆构成,于是求出凸包就搞定了。因为走一圈,经过拐点时,所形成的扇形的内角和是360度,总会形成一个完整的圆。故结果等于,凸包周长+一个完整的圆周长。

值得注意的是,这种题目精度往往比较坑,四舍五入!=(int)类型强制转换。

下面的程序套用的模板,比较全面,所以保留了lf(本题中int就行)。

求凸包主要思路就是先排序(y为第一关键字,x为第二关键字,不排序的话有可能会从最低点出发沿着左半凸走,每一次都是非法路径,就把点删完了,找不到凸包),找到左下角与右上角的两个点将整个凸包分为两部分(因为找右半凸的时候有可能找到左半凸上边的点,因为不合法就会删掉这个点,那么之后我们找左半凸的时候就会受影响)。用叉乘判断是否合法,叉乘结果为负时说明新的边在旧的边右侧,而因为我们是逆时针找凸包的(如果要顺时针找,那么叉乘为正不合法),凸包又是一个凸多边形,所以整个图形应当在任一凸包上边的左侧,也就是说,沿着凸包走只能左转。

通过这种方法就求得了凸包。

# include <cstdio>
# include <cstring>
# include <cstdlib>
# include <iostream>
# include <cmath>
# include <algorithm>
using namespace std;

#define rep(i,l,r) for(int i=l;i<=r;i++)
#define drep(i,r,l) for(int i=r;i>=l;i--)
#define min(a,b) (a<b?a:b)
#define max(a,b) (a>b?a:b)
#define LL long long
#define sqr(x) ((x)*(x))
#define suf(x) (x==n?1:x+1)
#define pi 3.1415926535897932384626433832795

const int N = 1010;

int n, l;

struct point{
double x , y;
point(){}
point(double x1 , double y1) {x = x1; y = y1;}
inline double operator%(const point &a)const{//叉乘
return x*a.y - y*a.x;
}
inline double operator*(const point &a)const{//点积
return x*a.x + y*a.y;
}
inline point operator+(const point &a)const{
return point(x + a.x , y + a.y);
}
inline point operator-(const point &a)const{
return point(x - a.x , y - a.y);
}
inline point operator*(double k)const{
return point(x*k , y*k);
}
inline point operator/(double k)const{
return point(x/k , y/k);
}
}a
, b
;

inline bool cmp(const point &a , const point &b){
//以y为第一关键字,x为第二关键字排序,保证从下到上
return a.y < b.y || a.y == b.y && a.x < b.x;
}

inline double Cross(point &a , point &b , point &c){
return (b-a) % (c-a);//向量ab叉乘向量ac
}

inline double dis(point &a , point &b){//两点之间距离
return sqrt(sqr(a.x-b.x) + sqr(a.y-b.y));
}

inline void Tubao(){
sort(a+1, a+n+1, cmp);
int m = 0;
rep(i,1,n){
while (m > 1 && Cross(b[m-1] , b[m] , a[i]) <= 0) m--;
//只有一条边时一定合法,叉乘<0时不合法,把中间所有不合法的点都去掉,加入这个新的点
b[++m] = a[i];//合法就保存
}
int j = m;
drep(i,n,1){
while (m > j && Cross(b[m-1] , b[m] , a[i]) <= 0) m--;
b[++m] = a[i];
}
n = m;
rep(i,1,n) a[i] = b[i];//返回到a(可无)
}

inline void Init(){
scanf("%d%d", &n, &l);
rep(i,1,n)
scanf("%lf%lf" , &a[i].x , &a[i].y);
Tubao();
}

inline void work(){
double ans = 0;
for(int i=1; i<n; i++){
ans += dis(a[i], a[i+1]);//凸包边长
}
ans += (2 * l) * pi;//圆周长(四舍五入->强转int)
printf("%d", (int)(ans + 0.5));
/*for(int i=1; i<=n; i++){
printf("\n%lf %lf", a[i].x, a[i].y);
}*/
}

int main(){
//while(scanf("%d%d", &n, &l) != EOF){
Init();
work();
//}
return 0;
}


这里给大家提供另一个方法,就是排序的时候按照极角排序,其实就是按照每个点与最低点连线的斜率从小到大排序(为什么用最低点呢?其实按理说找任意的点都是可以的,只不过我们为了不处理负斜率就选取最低点),这样的话就不用找最高点了,从最低点出发绕一圈即可。可以用反证法证明在寻找右半凸时不会找到左半凸,假设先找到了左半凸,那么就说明有至少一个右半凸上的点(连线)的斜率大于这个左半凸上的点(连线)的斜率,那么就是说从这个右半凸上的点走到这个左半凸上的点会向右转,不满足在凸包上的点的性质——只向右转。所以说得证。

下面附上代码:

#include<stdio.h>
#include<math.h>
#include<algorithm>
using namespace std;

const double pi = acos(-1.0);

struct point{
int x,y;
}a[100010],stack[100010];

double sum=0;
int n,k;

double dis(point a,point b){
return sqrt((double)(a.x-b.x) * (a.x-b.x) + (a.y-b.y) * (a.y-b.y));
}
int cj(point p0, point p1, point p2) {
return (p1.x-p0.x) * (p2.y-p0.y) - (p1.y-p0.y) * (p2.x-p0.x);
}

bool cmp(const point &aa, const point &b)
{
int ans = cj(a[0], aa, b);
if(ans > 0) return true;
else if(ans == 0 && dis(a[0], aa) < dis(a[0], b)) return true;
else return false;
}//极角排序

int main(){
int pos = 0;
scanf("%d%d", &n, &k);
point p0;
scanf("%d%d", &a[0].x, &a[0].y);
p0.x = a[0].x, p0.y = a[0].y;
for(int i=1; i<n; i++){
scanf("%d%d", &a[i].x, &a[i].y);
if((p0.y > a[i].y) || (p0.y == a[i].y) && (p0.x > a[i].x)){
p0.x = a[i].x, p0.y = a[i].y;
pos = i;
}
}
a[pos] = a[0];
a[0] = p0;
int top = 1;
sort(a+1, a+n, cmp);
if(n == 1){
top=0;stack[0]=a[0];
}
if(n == 2){
top = 1;
stack[0] = a[0];
stack[1] = a[1];
}
if(n > 2){
stack[0] = a[0], stack[1] = a[1];
for(int i=2; i<n; i++){
while(top>0 && cj(stack[top-1], stack[top], a[i]) <= 0) top--;
stack[++top] = a[i];
}
}
for(int i=0; i<=top; i++)
sum += dis(stack[i], stack[(i+1) % (top+1)]);
printf("%d", (int)(sum + 2 * pi * k + 0.5));
}


并不是笔者的代码,懒得写了,所以说凑合着看看吧,主要是排序那一块。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: