USACO Closed Fences 解题报告

几何题看着就很有畏惧感。这里用的是最naive的算法,时间复杂度应该在n^2。还没看别人的解题报告,不过我猜nlogn的解法是有的。

比如判断一个fence是不是valid的时候,这里将所有的线段两两比较,看是否相交。但是有个叫line sweep的算法,可以在nlogn的时间复杂度内完成。既然accept了,就懒得实现了。。。

判断两条线段(line segment)是否相交,stackoverflow上面有很详细的讨论,各种思路都有:http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect

我采用的是这里的解法:http://compgeom.cs.uiuc.edu/~jeffe/teaching/373/notes/x06-sweepline.pdf

大意是如果线段ab和cd相交,则点a和点b在线段cd的两边,且点c和点d在线段ab的两边。

前者看ac和cd以及bc和cd的向量叉乘符号是否一致。

上面链接上有详述,包括line sweep算法。

注意判断一条线端能否被观察者看到时,要排除到观察者在这条线延长线的情况。点(x,y)是否在线段(x1, y1)->(x2, y2)上面只需要判断:

(x1 - x2) * y - (y1 - y2) * x = x1 * y2 - x2 * y1

然后具体判断的时候,我用的是将整条线分成1000份,看观察者到这个等分点的连线是否与别的线相交了。

如果有一个点能看到,则这个线段能看到。

usaco标准解法里面取了中点,并且将第一条相交的线段标记为可见。这个想法很好,可以减少重复判断。

也可以先取中点,再二分,效率应该能更高。

我这种最简单。在这种小数据n=200的情况下也可以过所有测试点。


/* 
ID: thestor1 
LANG: C++ 
TASK: fence4 
*/
#include <iostream>  
#include <cmath>  
#include <cstdio>  
#include <cstring>  
#include <climits>  
#include <cassert>  
#include <string>  
#include <vector>  
#include <set>  
#include <queue>  
#include <stack>  
#include <algorithm>  
  
using namespace std;
const int MAXN = 200;
int X[MAXN], Y[MAXN];
int N;

// check if ac and cd is counter-clockwise
bool isCCW(double x1, double y1, double x2, double y2, double x3, double y3)
{
	// ac = (x2 - x2, y2 - y1)
	// cd = (x3 - x2, y3 - y2)
	return (x2 - x1) * (y3 - y2) - (y2 - y1) * (x3 - x2) > 0;
}

bool isIntersect(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4)
{
	if(isCCW(x1, y1, x3, y3, x4, y4) == isCCW(x2, y2, x3, y3, x4, y4))
	{
		return false;
	}
	else if(isCCW(x3, y3, x1, y1, x2, y2) == isCCW(x4, y4, x1, y1, x2, y2))
	{
		return false;
	}
	return true;
}

bool isValid()
{
	for(int i = 3; i < N; ++i)
	{
		for(int j = 1; j <= i - 2; ++j)
		{
			if(isIntersect(X[i - 1], Y[i - 1], X[i], Y[i], X[j - 1], Y[j - 1], X[j], Y[j]))
			{
				return false;
			}
		}
	}
	return true;
}

bool isColine(int lineno, int x, int y)
{
	int x1 = X[lineno];
	int y1 = Y[lineno];
	int x2 = X[(lineno + 1) % N];
	int y2 = Y[(lineno + 1) % N];
	return (x1 - x2) * y - (y1 - y2) * x == x1 * y2 - x2 * y1;
}

bool isSeen(int lineno, int x, int y)
{
	int x1 = X[lineno];
	int y1 = Y[lineno];
	int x2 = X[(lineno + 1) % N];
	int y2 = Y[(lineno + 1) % N];
	int nsegments = 1000;
	double px, py;
	for(int i = 1; i < nsegments; ++i)
	{
		px = x1 + i * 1.0 / nsegments * (x2 - x1);
		py = y1 + i * 1.0 / nsegments * (y2 - y1);
		bool blocked = false;
		for(int j = 0; j < N; ++j)
		{
			if(j == lineno)
			{
				continue;
			}
			int x2 = X[j];
			int y2 = Y[j];
			int x3 = X[(j + 1) % N];
			int y3 = Y[(j + 1) % N];

			if(isIntersect(x, y, px, py, x2, y2, x3, y3))
			{
				blocked = true;
				break;
			}
		}
		if(!blocked)
		{
			return true;
		}
	}
	return false;
}

int main()  
{
	FILE *fin  = fopen ("fence4.in", "r");
	FILE *fout = fopen ("fence4.out", "w");
	// cin>> N;
	fscanf(fin, "%d", &N); 
	// cout<<"N: "<<N<<endl;
	int x, y;
	// cin>>x>>y;
	fscanf(fin, "%d %d", &x, &y); 
	for(int i = 0; i < N; ++i)
	{
		fscanf(fin, "%d %d", &X[i], &Y[i]); 
		// cin>>X[i]>>Y[i];
		// cout<<i<<": "<<X[i]<<", "<<Y[i]<<endl;
	}
	if(!isValid())
	{
		fprintf(fout, "NOFENCE\n");
		// cout<<"NOFENCE"<<endl;
		return 0;
	}
	std::vector<int> v;
	for(int i = 0; i < N; ++i)
	{
		if(!isColine(i, x, y) && isSeen(i, x, y))
		{
			v.push_back(i);
		}
	}
	if(v.size() >= 2 && v[v.size() - 2] == N - 2)
	{
		v[v.size() - 2] = N - 1;
		v[v.size() - 1] = N - 2;
	}
	fprintf(fout, "%d\n", v.size());
	// cout<<v.size()<<endl;
	for(int i = 0; i < v.size(); ++i)
	{
		if(v[i] == N - 1)
		{
			fprintf(fout, "%d %d %d %d\n", X[0], Y[0], X[v[i]], Y[v[i]]);
			// cout<<X[0]<<" "<<Y[0]<<" "<<X[v[i]]<<" "<<Y[v[i]]<<endl;	
		}
		else
		{
			fprintf(fout, "%d %d %d %d\n", X[v[i]], Y[v[i]], X[v[i] + 1], Y[v[i] + 1]);
			// cout<<X[v[i]]<<" "<<Y[v[i]]<<" "<<X[v[i] + 1]<<" "<<Y[v[i] + 1]<<endl;
		}
	}
	return 0;  
} 


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值