现在的位置: 首页 > 综合 > 正文

UVa 10135 – Herding Frosh

2013年12月04日 ⁄ 综合 ⁄ 共 3335字 ⁄ 字号 评论关闭

题目:在平面上有一些点,从(0,0)出发,用一条丝带把所有的点都围起来,问最小长度。

分析:计算几何、凸包。求(0,0)与n个点的凸包,可分为两种情况:1.(0,0)在凸包上;2.(0,0)在凸包内。如下图:

            图1:                图2:

            其中黄色的点就是(0,0)。对于情况1直接输出凸包的周长即可;对于情况2有点复杂,假设图2里面的部分就是我们要求的解,那么我们可以得到下面的结论。如下图:

            图3:        图4:

            这里过(0,0)的直线,将点分成左右两个集合,其中直线上的点为一个集合所有,图3或图4两种情况(当图3向顺时针旋转就是图4,所以不必重复计算),黄色和绿色的点为共有,他们分别构成凸包,注意这里新加入了一个绿色节点,这是直线与凸包的交点。因此,我们这里枚举所有点和(0,0)构成的直线,求解两个凸包的周长和减掉黄色点和绿色点间的线段长度,求出最小值即可(这种情况可以包含情况1,不过特判一下可以提高效率)。这里还有一个优化(我的代码中没有使用),即每次只处理(0,0)与凸包相邻顶点的三角形的点,方法一样,不过这么分治了一下总体的代价会小很多。

#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cmath>

using namespace std;

//点结构 
typedef struct pnode
{
	double x,y,d;
	pnode( double a, double b ){x = a;y = b;}
	pnode(){}
}point;
point P0,D[1005],C[1005];
point S1[1005],S2[1005];

//线结构 
typedef struct lnode
{
	double x,y,dx,dy;
	lnode( point a, point b ){x = a.x;y = a.y;dx = b.x-a.x;dy = b.y-a.y;}
	lnode(){}
}line;
 
//叉乘ab*ac 
double crossproduct( point a, point b, point c )
{
	return (b.x-a.x)*(c.y-a.y)-(c.x-a.x)*(b.y-a.y);
}

//点到点距离 
double dist( point a, point b )
{
	return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}

//坐标排序 
bool cmp1( point a, point b )
{
	return (a.x==b.x)?(a.y<b.y):(a.x<b.x);
}

//级角排序 
bool cmp2( point a, point b )
{
	double cp = crossproduct( P0, a, b );
	if ( cp == 0 ) return a.d < b.d;
	else return cp > 0;
}

//计算凸包 
double Graham( point *P, int n, int &m )
{
	if ( n < 1 ) return 0;
	sort( P+0, P+n, cmp1 );
	for ( int i = 1 ; i < n ; ++ i )
		P[i].d = dist( P[0], P[i] );
	P0 = P[0];
	sort( P+1, P+n, cmp2 );
	int top = n-1;
	if ( n > 2 ) {
		top = 1;
		for ( int i = 2 ; i < n ; ++ i ) {
			while ( top > 0 && crossproduct( P[top-1], P[top], P[i] ) <= 0 ) -- top;
			P[++ top] = P[i];
		}
	}P[++ top] = P[0];
	m = top;
	
	double sum = 0.0;
	for ( int i = 0 ; i < top ; ++ i )
		sum += dist( P[i], P[i+1] );
	
	return sum;
}

//点与直线关系 
double to( point p, line l )
{
	return l.dx*(p.y-l.y)-l.dy*(p.x-l.x);
}

//点在线段上 
bool on( point p, line l )
{
	if ( l.dx*(p.y-l.y)-l.dy*(p.x-l.x) == 0 )
	if ( (p.x-l.x)*(p.x-l.x-l.dx) <= 0 )
	if ( (p.y-l.y)*(p.y-l.y-l.dy) <= 0 )
		return true;
	return false;
}

//点在多边形上
bool on( point p, point* P, int n )
{
	for ( int i = 0 ; i < n ; ++ i ) {
		line s = line( P[i], P[i+1] );
		if ( on( p, s ) ) return true;
	}return false;
}

//直线与线段相交 
bool cross( line a, point b, point c )
{
	double t1 = 0.0+a.dx*(b.y-a.y)-a.dy*(b.x-a.x);
	double t2 = 0.0+a.dx*(c.y-a.y)-a.dy*(c.x-a.x);
	return t1*t2 <= 0;
}

//两直线交点
point crosspoint( line l, point a, point b )  
{  
    line m = line( a, b );  
    if ( m.dx*l.dy == m.dy*l.dx ) {  
        if ( dist( point( l.x, l.y ), a ) < dist( point( l.x, l.y ), b ) )  
            return a;  
        else return b;  
    }else {  
        double a1 = -l.dy,b1 = l.dx,c1 = l.dx*l.y-l.dy*l.x;  
        double a2 = -m.dy,b2 = m.dx,c2 = m.dx*m.y-m.dy*m.x;  
        double x = (c1*b2-c2*b1)/(a1*b2-a2*b1);  
        double y = (c1*a2-c2*a1)/(b1*a2-b2*a1);  
        return point( x, y );  
    }   
}  

//求直线和凸多边形交点 
point cross( line l, point* P, int n )
{
	for ( int i = 0 ; i < n ; ++ i )
		if ( cross( l, P[i], P[i+1] ) ) {
			point p = crosspoint( l, P[i], P[i+1] );
			if ( p.x == l.x && p.y == l.y ) continue;
			if ( on( point(0,0), line( p, point( l.x, l.y ) ) ) )
				return p;
		}
	return point(0,0);
}

int main()
{
	int T,N,M,O;
	while ( scanf("%d",&T) != EOF ) 
	for ( int t = 1 ; t <= T ; ++ t ) {
		scanf("%d",&N);
		for ( int i = 0 ; i < N ; ++ i ) {
			scanf("%lf%lf",&D[i].x,&D[i].y);
			C[i].x = D[i].x;C[i].y = D[i].y;
		}C[N].x = C[N].y = 0.0;
		
		double ans = Graham( C, N+1, M );
		if ( !on( point(0,0), C, M ) ) {
			ans += 2.0*dist( point(0,0), C[0] );
			for ( int i = 0 ; i < N ; ++ i ) {
				if ( !D[i].x && !D[i].y ) continue;
				//构造直线将点分成两个集合
				line  l = line( D[i], point(0,0) );
				//计算直线与凸包交点 
				point p = cross( l, C, M );
	
				int   s1_count = 0,s2_count = 0;
				for ( int j = 0 ; j < N ; ++ j ) {
					double ptol = to( D[j], l );
					if ( ptol <  0 ) S1[s1_count ++] = D[j];
					if ( ptol >= 0 ) S2[s2_count ++] = D[j]; 
				}
				S1[s1_count ++] = point(0,0); S1[s1_count ++] = p;
				S2[s2_count ++] = point(0,0); S2[s2_count ++] = p;

				double ans1 = Graham( S1, s1_count, O )+Graham( S2, s2_count, O )-2.0*dist( point(0,0), p );
				if ( ans > ans1 ) ans = ans1;
			}
		}
		
		printf("%.2lf\n",ans+2.0);
		if ( t != T ) printf("\n");
	}
	return 0;
}

抱歉!评论已关闭.