圈水池(nyoj 78)

圈水池

时间限制: 3000 ms  |  内存限制: 65535 KB
难度: 4
描述
有一个牧场,牧场上有很多个供水装置,现在牧场的主人想要用篱笆把这些供水装置圈起来,以防止不是自己的牲畜来喝水,各个水池都标有各自的坐标,现在要你写一个程序利用最短的篱笆将这些供水装置圈起来!(篱笆足够多,并且长度可变)
输入
第一行输入的是N,代表用N组测试数据(1<=N<=10)
第二行输入的是m,代表本组测试数据共有m个供水装置(3<=m<=100)
接下来m行代表的是各个供水装置的横纵坐标
输出
输出各个篱笆经过各个供水装置的坐标点,并且按照x轴坐标值从小到大输出,如果x轴坐标值相同,再安照y轴坐标值从小到大输出
样例输入
1
4
0 0
1 1
2 3
3 0
样例输出
0 0
2 3
3 0

这是个凸包入门题。

平面凸包 :

定义: 对一个简单多边形来说,如果给定其边界上或内部的任意两个点,连接这两个点的线段上的所有点都被包含在该多边形的边界上或内部的话,则该多边形为凸多边形。

以下来自:点击打开链接

Graham扫描法

基本思想:通过设置一个关于候选点的堆栈s来解决凸包问题。

操作:输入集合Q中的每一个点都被压入栈一次,非CH(Q)(表示Q的凸包)中的顶点的点最终将被弹出堆栈,当算法终止时,堆栈S中仅包含CH(Q)中的顶点,其顺序为个各顶点在边界上出现的逆时针方向排列的顺序。

注:下列过程要求|Q|>=3,它调用函数TOP(S)返回处于堆栈S 顶部的点,并调用函数NEXT-TO –TOP(S)返回处于堆栈顶部下面的那个点。但不改变堆栈的结构。

GRAHAM-SCAN(Q)

1           设P0 是Q 中Y 坐标最小的点,如果有多个这样的点则取最左边的点作为P0;

2           设<P1,P2,……,Pm>是Q 中剩余的点,对其按逆时针方向相对P0 的极角进行排序,如果有数个点有相同的极角,则去掉其余的点,只留下一个与P0 距离最远的那个点;

3           PUSH(p0 , S)

4           PUSH(p1 , S)

5           for i  2 to m

6               do while 由点NEXT-TOP-TOP(S),TOP(S)和Pi 所形成的角形成一次非左转

7                   do POP(S)

8               PUSH(pi , S)

9        return S

 

首先,找一个凸包上的点,把这个点放到第一个点的位置P0。然后把P1~Pm 按照P0Pi的方向排序,可以用矢量积(叉积)判定。

做好了预处理后开始对堆栈中的点<p2,p3,p4,...,pm>中的每一个点进行迭代,在6到8行的while循环把发现不是凸包中的顶点的点从堆栈中移去。(原理:沿逆时针方向通过凸包时,在每个顶点处应该向左转。因此,while循环每次发现在一个顶点处没有向左转时,就把该顶点从堆栈中弹出。)当算法向点pi推进、在已经弹出所有非左转的顶点后,就把pi压入堆栈中。

举例如下:

                            

                                  

                            

                                 

                                  

                                                         


这一道题还需要做的就是在最后对点进行排序

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

struct node
{
	int x, y;
}p[110], stack[110];
int top;

double dis(node a, node b)
{//计算两点之间距离 
	return (double)((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}

int multi(node a, node b, node c)
{//计算叉积 
	return (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y);
}

int cmp(node a, node b)
{//极角排序,角度相同则距离小的在前面 
	int m = multi(a, b, p[0]);
	if(m > 0)
		return 1;
	if(m == 0 && dis(a, p[0]) < dis(b, p[0]))
		return 1;
	return 0;
}

int cmp1(node a, node b)
{ 
	if(a.x != b.x)
		return a.x < b.x;
	else return a.y < b.y;
}

void input(int pnum)
{
	int k = 0;
	for(int i = 0; i < pnum; i++)
	{
		scanf("%d %d", &p[i].x, &p[i].y);
		//找p0
		if(p[i].y < p[k].y || (p[i].y == p[k].y && p[i].x < p[k].x))
			k = i;
	}
	//将左下角的点放在p[0]位置	
	node temp = p[0];
	p[0] = p[k];
	p[k] = temp; 
	//排序
	sort(p + 1, p + pnum, cmp);
} 

void graham(int pnum)
{
	if(pnum == 1)
	{
		top = 0;
		stack[0] = p[0];
		
	}
	if(pnum == 2)
	{
		top = 1;
		stack[0] = p[0];
		stack[1] = p[1];
	}
	if(pnum > 2)
	{
		top = 1;
		stack[0] = p[0],stack[1] = p[1]; 
		for(int i = 2; i < pnum; i++)
		{
			//如果 边stack[top - 1]-p[i]在边stack[top - 1]-stack[top]的左边,
			//则要将栈顶弹出
			//一开始把此处的while写成if 
			while(top > 0 && multi(stack[top - 1], p[i], stack[top]) > 0)
				top --;
			stack[++top] = p[i];
		} 
	}
}

int main (void)
{
	int ncase;
	scanf("%d", &ncase);
	while(ncase--)
	{
		int pnum;
		scanf("%d", &pnum);
		input(pnum);
		graham(pnum);
		sort(stack, stack + top + 1, cmp1);	//对输出进行排序 
		for(int i = 0; i <= top; i++)
			printf("%d %d\n", stack[i].x, stack[i].y);		 
	}
	return 0;
} 



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值