UESTC1560 Division(四川2011年省赛 D,计算几何)

说是逆时针给出一个凸多边形,然后给出凸多边形外一点,以这个点为起点做射线,要求该射线把多边形面积均分成两份,输出这条射线的单位向量

开始我觉得是求出射线可能的张角范围,然后离散张角弧度,但是由于计算时需要多次用到三角函数,精度损失太严重(离散度我一度开到1e-8,机器跑了好久才出结果,结果误差还是不能容忍 = =!),我去啊……

没办法,最后想到改成离散线段,先求出凸多边形上点集相对于射线起点的两个边界值(下图B点和C点),确定张角范围,然后二分枚举这条线段上的点

如下图中,AD连一条直线,这条直线把多边形分成两部分,计算其中一部分面积,和总面积的一半比较就可以了

至于计算面积的方法,我用的是半平面交,复杂度其实挺高的,编码还比较困难(最近一直练,所以就用了,嘿),其实可以先求 AD 和多边形的交点,然后求多边形面积,复杂度较低,编码还比较简单

UESTC1560 Division(四川2011年省赛 D,计算几何)_第1张图片


代码:

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

const int N = 101;
const double eps = 1e-8;
const double pi = acos(-1.0);

struct cpoint {//C++构造函数,默认缺省值为(0,0)
    double x, y;
    cpoint(double xx = 0, double yy = 0): x(xx), y(yy) {};
};
int dcmp(double x) {//判断参数的符号,负数返回-1,0返回0,正数返回1
    if (x < -eps) return -1; 
	else return x > eps;
}

double xmult(cpoint p0, cpoint p1, cpoint p2) { // p0p1 与 p0p2 叉积
    return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y);
}

cpoint kp;

double dis2(cpoint a,cpoint b)
{
	return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y);
}

bool cmp_angle(cpoint a,cpoint b)
{
	double xx=xmult(kp,a,b);
	if(dcmp(xx)==0)
		return dcmp(dis2(a,kp) - dis2(b,kp))==-1?1:0;
	else return dcmp(xx)==1?1:0;
}

bool EqualPoint(cpoint a, cpoint b) {//两点相等
    return dcmp(a.x - b.x) == 0 && dcmp(a.y - b.y) == 0;
}

struct cvector {//向量
    cpoint s, e;
    double ang, d;
};

//atan (double x)弧度表示的反正切
//atan2(double x,double y)弧度表示的反正切,相当于atan(x/y)

void setline(double x1, double y1, double x2, double y2, cvector &v) {
    v.s.x = x1; v.s.y = y1;
    v.e.x = x2; v.e.y = y2;
    v.ang = atan2(y2 - y1, x2 - x1);
	//这里的 d 代表向量(直线)和坐标轴的截距,正数表示 原点 在该向量的左边
	//(这道题要求左半平面交),负号则表示 原点 在右边
    if (dcmp(x1 - x2))          // x1 > x2
		v.d = (x1 * y2 - x2 * y1) / fabs(x1 - x2);
    else v.d = (x1 * y2 - x2 * y1) / fabs(y1 - y2);
}

//判向量平行
bool parallel(const cvector &a, const cvector &b) {
    double u = (a.e.x - a.s.x) * (b.e.y - b.s.y) - (a.e.y - a.s.y) * (b.e.x - b.s.x);
    return dcmp(u) == 0;
}

//求两向量(直线)交点 (两向量不能平行或重合)
cpoint CrossPoint(const cvector &a, const cvector &b) {
    cpoint res;
    double u = xmult(a.s, a.e, b.s), v = xmult(a.e, a.s, b.e);
    res.x = (b.s.x * v + b.e.x * u) / (u + v);
    res.y = (b.s.y * v + b.e.y * u) / (u + v);
    return res;
}

//半平面交排序函数[优先顺序: 1.极角 2.前面的直线在后面的左边]
static bool VecCmp(const cvector &l, const cvector &r) {
    if (dcmp(l.ang - r.ang)) return l.ang < r.ang;
    return l.d < r.d;
}

cvector deq[N]; //用于计算的双端队列

void HalfPanelCross(cvector vec[],int n,cpoint cp[],int &m)
{
	int i,tn;
	sort(vec,vec+n,VecCmp);
	for(tn=i=1;i<n;i++)
	{
		if(dcmp(vec[i].ang-vec[i-1].ang) != 0)
			vec[tn++]=vec[i];
	}n=tn;
	int bot=0, top=1;
	deq[0]=vec[0], deq[1]=vec[1];
	for(i=2;i<tn;i++)
	{
		if(parallel(deq[top],deq[top-1]) || parallel(deq[bot],deq[bot+1]))return ;
		while(bot<top && dcmp(xmult(vec[i].s,vec[i].e,CrossPoint(deq[top],deq[top-1])))<0)
			top--;
		while(bot<top && dcmp(xmult(vec[i].s,vec[i].e,CrossPoint(deq[bot],deq[bot+1])))<0)
			bot++;
		deq[++top]=vec[i];
	}
	while(bot<top && dcmp(xmult(deq[bot].s,deq[bot].e,CrossPoint(deq[top],deq[top-1])))<0)
		top--;
	while(bot<top && dcmp(xmult(deq[top].s,deq[top].e,CrossPoint(deq[bot],deq[bot+1])))<0)
		bot++;
	
	if(top <= bot+1)return ;
	
	for(m=0,i=bot;i<top;i++)
		cp[m++]=CrossPoint(deq[i],deq[i+1]);
	if(bot<top+1)
		cp[m++]=CrossPoint(deq[bot],deq[top]);
	m=unique(cp,cp+m,EqualPoint)-cp;
}

double PolygonArea(cpoint p[], int n) {
    if (n < 3) return 0;
    double s = p[0].y * (p[n - 1].x - p[1].x);
    for (int i = 1; i < n; ++i)
        s += p[i].y * (p[i - 1].x - p[(i + 1) % n].x);
    return fabs(s / 2); // 顺时针方向s为负
}
int n,m;
cvector v[N],vx[N];
cpoint cp[N];

int main()
{
	int i,t,time=1;
	cpoint p[N],px[N],mid,low,high;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d",&n);
		memset(p,0,sizeof(p));
		
		for(i=0;i<n;i++)//题目数据保证逆时针给出点集
			scanf("%lf%lf",&p[i].x,&p[i].y);
		p[n]=p[0];

		double s_all=PolygonArea(p,n);
		
		for(i=0;i<n;i++)
			setline(p[i].x,p[i].y,p[i+1].x,p[i+1].y,v[i]);
		memcpy(vx,v,sizeof(v));//v[]需要保存,因为每次求半平面交都会排序
		
		scanf("%lf%lf",&kp.x,&kp.y);
		
		sort(p,p+n,cmp_angle);//角排序,找到边界点,当然也可以直接找最大值最小值
		
		low=p[0]; high=p[n-1];
		while(!EqualPoint(low,high))//二分线段,大大优化了求解过程
		{
			mid.x=(low.x+high.x)/2;
			mid.y=(low.y+high.y)/2;
			memcpy(v,vx,sizeof(vx));

			setline(kp.x,kp.y,mid.x,mid.y,v[n]);//把得到的边加入向量集合

			HalfPanelCross(v,n+1,cp,m);//向量(直线)集合,长度;点集,长度
			double s_half=PolygonArea(cp,m);
			if(dcmp(2*s_half-s_all)==0)break;
			if(dcmp(2*s_half-s_all)==1)low=mid;
			else high=mid;
		}
		double x=atan2(mid.y-kp.y,mid.x-kp.x);
		printf("Case #%d: ",time++);
		printf("%.4lf %.4lf\n",cos(x),sin(x));
	}
	return 0;
}

你可能感兴趣的:(c,优化,struct,ini,parallel)