POJ1696-Space Ant

全解题报告索引目录 -> 【北大ACM – POJ试题分类

转载请注明出处:http://exp-blog.com

-------------------------------------------------------------------------

 

大致题意:

一只蚂蚁,只会向左转,现在给出平面上很多个点,求解一种走法,
能使得蚂蚁能经过的点最多,每个顶点该蚂蚁只能经过一次,且所行走的路线不能发生交叉.

 

解题思路:

凸包的入门水题,是凸包的一个变形
网上看到很多人copy别人的,说什么“极坐标排序”,那是Graham Scan Algoruthm的做法!虽然Graham只有O(nlogn) ,但是这题完全没必要用它,因为题目的规模很小,我用卷包裹算法照样0 ms 一次AC 。确实理论上卷包裹的O(n^2)不如Graham快,但是规模这么小的题目是反映不出来的。

Graham能不用就不用,一代码超长超烦,特别是散点集排序。看过算法理论的同学,一般都宁愿用极坐标而不用水平序,但是极坐标排序的比较规则特容易出错。水平排序我没看懂,估计其他菜鸟们也差不多。

 

解题的例图题目已经给出,我就不贴了,反正最终的路线就是螺旋形的。要求从 纵坐标 最小的点作为出发点,这个在输入时顺便找出来,可以节省一点点时间= =

其实无论输入什么样的点集,一定可以走完全部n个点的,这是凸包的性质决定,所以就放手去做了,不用担心走不完的情况

 

唯一注意的是,前面提到过时凸包的变形,是因为螺旋线是不封闭的,凸包是封闭的。要想不封闭很简单,做一个标记就可以了,每构造一个凸包顶点,就标记该点,不再与其连线

当连线完前面n-1个点后,第n个点(最后没被标记的点)就不用再做了,一定是最后一点,输出它就可以了

 

其他细节参见我的程序, 卷包裹算法 百度就有了,这里我就不多说了

 

 

 

 

 

//Memory Time 
//228K   0MS 

#include<iostream>
using namespace std;

const int inf=101;

typedef class
{
	public:
		int x,y;
}point;

/*AB距离平方*/

int distsquare(point A,point B)
{
	return (B.x-A.x)*(B.x-A.x)+(B.y-A.y)*(B.y-A.y);
}

/*叉积计算*/

int det(int x1,int y1,int x2,int y2)
{
	return x1*y2-x2*y1;
}

int cross(point A,point B,point C,point D)
{
	return det(B.x-A.x , B.y-A.y , D.x-C.x , D.y-C.y);
}

int main(int i)
{
	int test;
	cin>>test;
	while(test--)
	{
		int n;   //n个点
		cin>>n;
		point* node=new point[n+1];  //n个点坐标
		int* conbag=new int[n+1];    //凸包顶点(根据顶点构造顺序,依次记录node[]下标)
		bool* vist=new bool[n+1];    //标记已作为凸包顶点的点

		/*Input*/

		int min_y=inf;    //最小纵坐标值
		int fi=0;
		for(i=1;i<=n;i++)
		{
			int num;
			cin>>num>>node[i].x>>node[i].y;
			vist[i]=false;

			if(min_y > node[i].y)
			{
				min_y = node[i].y;
				fi=i;
			}
		}
		conbag[1]=fi;  //起点
		conbag[0]=1;  //凸包顶点计数器
		vist[fi]=true;

		/*Structure Convex bag*/

		int pc=1;   //conbag[]指针
		while(true)
		{
			int s=conbag[pc];  //最新的凸包顶点
			int k;    //当前待加入的凸包顶点
			for(i=1;i<=n;i++)   //寻找当前基准向量sk,k取任意没标记的点就可以了
				if(!vist[i])
				{
					k=i;
					break;
				}

			for(i=1;i<=n;i++)   //枚举没标记的点i,计算sk X si的值,寻找最优(最外面的)k点作为凸包顶点
				if(i!=k && !vist[i])
				{
					int temp=cross(node[s],node[k],node[s],node[i]);

					if(temp<0)
						k=i;
					else if(temp==0)
						if(distsquare(node[s],node[k]) > distsquare(node[s],node[i]))  //当k i共线时,距离s近的点优先选取
							k=i;
				}

			conbag[++pc]=k;   //更新凸包顶点
			conbag[0]++;
			vist[k]=true;

			if(n-conbag[0]==1)   //处理完前面n-1个点后 第n个点不再处理
				break;
		}

		cout<<conbag[0]+1<<' ';  //这里输出n也可以的

		fi=0;
		for(i=1;i<=conbag[0];i++)
		{
			cout<<conbag[i]<<' ';   //输出前面n-1个点的同时寻找第n个没标记的点
			if(!vist[i])
				fi=i;
		}
		if(fi)
			cout<<fi<<endl;
		else
			cout<<n<<endl;

		delete node;
		delete conbag;
		delete vist;

	}
	return 0;
}


 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值