极角排序 POJ1696

VJ题目连接

题目大意

一种奇怪的虫子不能右转且走过路线之间不能有交点,吃植物才能存活,给出植物的坐标,求虫子要怎样走才能活得最久(吃的植物越多活越久)

输入:样例数,n组样例,每组给出一个n,然后n行每行给出3个数,分别是植物编号、植物x坐标、植物y坐标

输出:能吃的最大植物数目,并给出路线

解体思路

因为虫子只能左转且路线不能有交点,很容易想到让虫子逆时针螺旋地去吃植物,由外到内,可以将所有植物吃完。因为逆时针路线一定是左转,螺旋线保证不相交,从最外到最内是肯定可以吃完所有植物的。

怎样构造这样的螺旋线呢?这要用极角排序。从最下方的点开始,将这个点作为基点,对其他所有点进行极角排序,然后取极角最小点,重新设立为基点,对剩余点重新排序,依次类推直至剩最后一个点。

为什么要从最下点开始?因为这样可以保证其他点都在上方,这样就可以使其他点与该点的极角都>=0,这样可以以x轴正方向为基线找出夹角最小的点。之后由找到的新点为基点,可以保证其他未选择的点都在上一个点与新基点连线的一侧,这样就可以以这条连线作为基线求最小极角。

怎样求对极角进行排序?简单的做法是用sort函数,然后自定义一个cmp比较函数,大致如下:

//极角排序规则 
bool cmp(const Point &p1, const Point &p2)  
{  
    double tmp;  
    tmp=multiply(p1,p2,PointSet[cnt]);  
    //外积大于0 
    if(tmp>0)
    {  
        return true;  
    }  
    //两线重合时选择距离更小的 
    else if(fabs(tmp)<=eps && dis(PointSet[cnt],p1)<dis(PointSet[cnt],p2))
    {  
        return true;  
    }  
    return false;  
} 

这里没有直接求角度,而是利用外积来判断,外积函数如下:

//小于0,说明向量p0p1的极角大于p0p2的极角
double multiply(Point p1,Point p2,Point p0)
{
    return((p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y));
}

显然,若p1p0与p2p0的外积>0,则p1在p2右侧(若以上述的基线为水平线),相反,若外积<0,则p1在p2左侧,而我们要的极角最小的点即在最右侧的点。若外积为0,则三点共线,我们需要优先选择更靠近基点的点。

AC代码

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

struct Point
{
    double x,y,num;
};

const int maxN=55;
Point PointSet[maxN];       //输入的点集 
Point ans[maxN];            //输出的点集 
int n;      //点的个数 
int cnt;    //当前判断基点 

//小于0,说明向量p0p1的极角大于p0p2的极角
double multiply(Point p1,Point p2,Point p0)
{
    return((p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y));
}
//p1p2距离 
double dis(Point p1,Point p2)
{
    return(sqrt((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y)));
}
//极角排序规则 
bool cmp(const Point &p1, const Point &p2)  
{  
    double tmp;  
    tmp=multiply(p1,p2,PointSet[cnt]);  
    //外积大于0 
    if(tmp>0)
    {  
        return true;  
    }  
    //两线重合时选择距离更小的 
    else if(fabs(tmp)<=eps && dis(PointSet[cnt],p1)<dis(PointSet[cnt],p2))
    {  
        return true;  
    }  
    return false;  
} 

int main()
{
    int cas;
    scanf("%d",&cas);
    while(cas--)
    {
        scanf("%d",&n);
        memset(PointSet,0,sizeof PointSet);
        int i;
        for(i=0;i<n;i++)
        {
            scanf("%d%lf%lf",&PointSet[i].num,&PointSet[i].x,&PointSet[i].y);
            //找出最下点 
            if(PointSet[i].y<PointSet[0].y)
            {
                Point tmp=PointSet[0];
                PointSet[0]=PointSet[i];
                PointSet[i]=tmp;
            }
        }
        //初始基点 
        cnt=0;
        //排序 
        sort(PointSet+1,PointSet+n,cmp);
        ans[cnt]=PointSet[cnt++];
        for(i=2;i<n;i++)
        {
            //依次设立基点排序 
            sort(PointSet+cnt,PointSet+n,cmp);
            ans[cnt]=PointSet[cnt++];
        }
        ans[cnt]=PointSet[cnt++];
        printf("%d",cnt);
        for(i=0;i<cnt;i++)
        {
            printf(" %d",ans[i].num);
        }
        printf("\n");
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值