例4.10 POJ3525/LA3890离海最远的点 半平面交 + 二分法 + double小数点后有效位数处理方式/printf与g++c++的问题
Posted 嚜寒
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了例4.10 POJ3525/LA3890离海最远的点 半平面交 + 二分法 + double小数点后有效位数处理方式/printf与g++c++的问题相关的知识,希望对你有一定的参考价值。
0)
题意:
题意很简单,给出一张四面环海的岛屿的地图,岛屿用顶点表示(题目数据保证岛屿是凸多边形——所谓凸多边形与凹多边形区别,凸多边形就是把一个多边形任意一边向两方无限延长成为一条直线,如果多边形的其他各边均在此直线的同旁,那么这个多边形就叫做凸多边形。)找出岛屿上距离大海距离最长的一个点。即求岛屿上距离岛屿各条边边中最短的距离是所有点中最长的那个点。即求岛屿中的内接圆的圆心点。输出这个点到岛屿的边的最短的距离。即该岛屿中那个内接圆的半径...
分析:
半平面交求内核点集是一个点的情况(用精度控制),这里用二分法逼近。
由x 、y范围(0~10000),假定一个足够大的矩形范围(如果长、宽均为0~10000,则矩形对角线长度=sqrt(10000*10000*2),这个长度<10000*2),所以这个足够大的矩形内最长直径应该小于20000,我们用RRR=200000做上限,用LLL=0做下限,然后求mid=(LLL+RRR)/2,不断二分逼近,每逼近一次就求一次多边形内核的点集,直到RRR-LLL<1e-6,达到题目所要的精度要求,即认定这个范围内的点集可以被当做是一个点,如果这个点集不为空的话。我们就找到了这个点,且所要输出的距离即LLL或者RRR。
注意:
①半平面交+二分法,细节很多,容易出错,哪怕是代模板,要理解清楚每一个细节并足够小心。(每一步为什么,已尽可能将理解到的地方注释在下面的代码中,在此便不再赘述。)
②关于双精度问题,在OJ上用C++AC 、 G++WA。
解决办法:
//用printf控制浮点数的格式输出:(如果有输入,且用scanf的话,都用%lf,即scanf("%lf",&f))
printf("%.6lf\\n",LLL); //%lf 只有c++ 过了;
printf("%.6f\\n",LLL);//%f c++、g++、 本地, 都过了
//直接用cout控制浮点数的格式输出:(fixed是小数点表示,setprecision(n)是表示一共输出几位有效数字,两者合用,可以控制小数点后输出几位有效数字。拓展,scientific是指数方式表示)
cout<<fixed<<setprecision(6)<<LLL<<endl; g++ 本地 c++ 都过了 //要包含#include<iomanip>
cout <<setiosflags(ios::fixed)<<setprecision(6)<<LLL<<endl;//与上一句一样,另一种写法
cout <<setiosflags(ios::fixed);cout<<<setprecision(6)<<LLL<<endl;//与上一句一样,另一种写法 //说明cout <<setiosflags(ios::fixed);类似一个开关,打开以后,这之后的所有cout都会类似加一个fixed的作用下输出的效果
因为本地测试与提交OJ的g++结果相同,因此认为windows下codeblocks,与oj上(至少是poj)的g++评判效果相近
//拓展,用cout控制浮点数的格式输出,并用指数形式表示:
cout <<setiosflags(ios::scientific)<<amount <<endl; cout <<setiosflags(ios::fixed);//加第二句是将“开关”调回来,之后的输出重新用小数点来表示
原因:最早的编译标准,是将double,看做long float,所以在printf中才会有%lf的写法,随着硬件成本降低,内存容量变得越来越大,用%f就可以输出double精度的数据而不用担心内存爆掉,所以对于C++最新标准来说,这两种%f 、%lf对于printf输出都被接受了;但是对于g++,却只接受printf用%f输出而scanf依然用%lf读入。(windows下codeblocks采用一般情况下的GNU编译,效果同OJ上的g++提交方式)(g++, scanf只能 %lf 输入, printf 只能 %f 输出已测试;C++ ,printf %f 、%lf都可以也已测试,scanf能用%lf,%f读入未测试)
参考资料:fixed和setprecision对于cout控制double数据输出小数点后固定位数的有关写法,
1)
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <vector>
#include <math.h>
#include <algorithm>
#include<iomanip>
//Point point, Vecor vector 注意不要混淆
using namespace std;
const double eps=1e-6;
struct Point{
double x;
double y;
Point(double a=0,double b=0):x(a),y(b){}
};
typedef Point Vector;
struct Line{
Point p;//该线上某一点
Vector v;//该线的向量表示,用来指示方向
double ang;//该线的极角,即x正半轴旋转到该线向量的角度
Line(){} //???
Line(Point p,Vector v):p(p),v(v){ang=atan2(v.y,v.x) ;}// 返回极角
bool operator <(const Line&L)const{
return ang<L.ang; //重载Line的<,按极角由小到大排序,方便直接使用sort
}
};
//关于向量的+ - × 的重载:(vector继承了Point,所以vector对这些符号的重载也会使Point获得同样的重载功能,比如PolygonArea()函数中)
Vector operator +(Vector a,Vector b){return Vector(a.x+b.x,a.y+b.y);}
Vector operator - (Vector a,Vector b){return Vector(a.x-b.x,a.y-b.y);}
Vector operator * (Vector a,double p){return Vector(a.x*p,a.y*p);}
//关于向量的部分函数:
double Dot(Vector a,Vector b){return a.x*b.x+a.y*b.y;}//内积//点积//数量积
double Cross(Vector a,Vector b){return a.x*b.y-a.y*b.x; }//外积//叉积//向量积//叉乘
double Lenth(Vector a){return sqrt(Dot(a,a)); }//向量的模
//向量a的单位法向量:
Vector Normal(Vector a){
double L=Lenth(a);
return Vector(-a.y/L,a.x/L);
}
//计算凸多边形的面积:
//利用 相邻向量的叉乘/2==三角形面积,将凸多边形从一个点出发连接其他非相邻点得到有限个三角形
double PolygonArea(vector <Point> p){
int n=p.size();
double area=0;
for(int i=1;i<n-1;i++){
area+=Cross(p[i]-p[0],p[i+1]-p[0]);
}
return area/2;
}
//判断点P是否在向量L的左边:(向量p-L.p的方向是从L.p出发指向p)
bool OnLeft(Line L,Point p){
return Cross(L.v,p-L.p)>0;
}
Point GetLineIntersection(Line a,Line b){
Vector u=a.p-b.p;
double t=Cross(b.v,u)/Cross(a.v,b.v);
return a.p+a.v*t;
}
vector<Point> HalfPlaneIntersection(vector <Line> L){//英文,半平面交
int n=L.size();
sort(L.begin(),L.end());//按极角有小到大排序,Line里将<重载了
int first,last;
vector<Point> p(n);//界定了vector的大小?//存储当前切割后多边形的确定的顶点
vector<Line> q(n);//存储当前切割后多边形的确定的向量
vector<Point> ans;//存储最终的内核点集
q[first=last=0]=L[0];
for(int i=1;i<n;i++){
//(为什么当前向量L[i]切割当前多边形形成新的多边形只对第一条确定的向量边和最后一条向量边有关呢。因为之前按极角由小到大排序。)
while(first<last&&!OnLeft(L[i],p[last-1])){ last--; }//判断该向量L[i]切割当前多边形,与最后一个确定的顶点的位置关系,如果最后一个确定的顶点不在该向量的左边,即最后一个确定的顶点在该向量的外侧,那么当前向量进入后,最后一条确定的边就会在新形成的多边形的外面(因为是按极角由小到大,所以不会出现上一条边有一部分在新形成的多边形的外面、另一部分在里面的情形)
while(first<last&&!OnLeft(L[i],p[first])){ first++; }
//判断该向量L[i]切割当前多边形,与第一个确定的顶点的位置关系,如果第一个确定的定点不在该向量的左边,同样说明第一个确定的定点在该向量的外侧,那么当前向量进入后,第一条确定的边就会在新形成的多边形的外面
q[++last]=L[i];//last++ 与 ++last 不同
if(fabs(Cross(q[last].v,q[last-1].v))<eps){//如果这紧邻的两条向量边方向相同,留内侧的(因为按极角排序所以极角相同的向量边是相邻的)
last--;
if(OnLeft(q[last],L[i].p)){
q[last]=L[i];//判断谁在内侧
}
}
if(first<last){
p[last-1]=GetLineIntersection(q[last-1],q[last]);//得到当前切割后形成的多边形的确定的最新的点
}
}
while(first<last&&!OnLeft(q[first],p[last-1])) last--;//判断新得到的多边形最后一个确定的点与第一条边的位置关系,如果点在外侧,同样舍去现在多边形的已确定的最后一条边
if(last-first<=1) return ans;//如果是空集,就返回空的vector
p[last]=GetLineIntersection(q[last],q[first]);//将最后一个交点纳入新得到的多边形的确定的点集之中
for(int i=first;i<=last;i++){
ans.push_back(p[i]);//将确定的顶点放入新的vector中并返回
}
return ans;
}
int main()
{
int num;
while(~scanf("%d",&num)&&num){
vector <Point> p,v,normal;
int x,y;
for(int i=0;i<num;i++){
scanf("%d%d",&x,&y);
p.push_back(Point(x,y));
}
//如果用叉乘求得的多边形面积<0,说明是顺时针输入的点,用库函数reverse进行数组的反转:
if(PolygonArea(p)<0)
reverse(p.begin(),p.end());
for(int i=0;i<num;i++){
v.push_back(p[(i+1)%num]-p[i]);
normal.push_back(Normal(v[i]));//求得并存储各个向量的法向量
}
double LLL=0;
double RRR=20000;
//为什么RRR是20000呢。x 、y都是从0到10000,所以四个点组成的矩形的对角线是sqrt(2*10000*10000)<20000,是这样理解吗?
while(RRR-LLL>0.000001){ //1e-6 是0.000001,当RRR-LLL<1e-6时,求得的内核点集,可被认为是一个点
//每一次while循环都是一次二分推进
vector<Line>L;
double mid=LLL+(RRR-LLL)/2;
//for遍历所有边往前推进
for(int i=0;i<num;i++){
L.push_back(Line(p[i]+normal[i]*mid,v[i]));//存储各个向量边推进mid距离以后得到的向量边,即推进后得到的新的多边形
}
vector<Point>poly=HalfPlaneIntersection(L);//通过nlogn时间复杂度的直线切割,得到的这一次二分推进后的新的多边形的内核点集
if(poly.empty()){//cout<<"1:"<<mid<<endl;
RRR=mid;
}//如果内核点集是空的,说明这一次推进mid距离过大,将上限重新界定随之得到新的mid推进距离,并且重新推进
else{
//cout<<"2:"<<mid<<endl;
LLL=mid;//点集不为空,将下限重新界定,等待下一次while的条件语句进行判断看是否还需要下一次的二分推进
}
}
cout <<setiosflags(ios::fixed)<<setprecision(7)<<LLL<<endl;
//printf("%.6lf\\n",LLL);//输出6位有效数字以满足题目要求,lf是double的输出格式,另外输出LLL或者RRR都可以的吧。
}
}
以上是关于例4.10 POJ3525/LA3890离海最远的点 半平面交 + 二分法 + double小数点后有效位数处理方式/printf与g++c++的问题的主要内容,如果未能解决你的问题,请参考以下文章
POJ - 2187 Beauty Contest(最远点对)