POJ 1418 Viva Confetti

题意: 平面上有若干个圆盘, 它们之间有覆盖关系. 从最底层的圆盘开始, 依次给出每个圆盘的圆心坐标和半径, 求最终有多少个圆盘可见. (可以看到任何一个部分就算可见) (数据保证在微小扰动下答案不变)

 

         此题见于《训练指南》, 然而上面讲的稍嫌模糊, 我再整理整理...

         首先要找到圆盘可见的充要条件. 由于处理整个圆面不好处理, 最好转化为处理圆弧(圆周), 因此可以把圆盘可见分为两类: 有弧可见, 没有弧可见. 或者说, 第一类就是圆盘的边缘是可见的; 第二类是圆盘的边缘都不可见, 但是中间是可见的.

         不论对于哪一类, 都要把圆周划分为圆弧. 对于某个圆盘, 自然是要按照这个圆和其他圆的交点来划分这个圆的圆周. 因此求这个圆和其他圆的所有交点(两圆求交点是基本问题), 然后所有交点就把这个圆周分成了若干个"独立圆弧", 也就是没有任何圆周与这个弧的中间相交. 所以一条独立圆弧要么都可见, 要么都不可见, 即一条独立圆弧的可见性等价于该圆弧内部任意一点的可见性, 方便起见可以用中点. 因此求出所有独立圆弧的中点, 然后对于每个中点, 遍历该圆上方的所有圆, 看该中点是否被某个圆盖住了, 如果有某个中点没有被任何圆盖住, 那么当前这个圆就是第一类可见的. 当然, 也可以在从上往下的遍历过程中存下哪些圆盘是可见的, 然后判断是否被盖住时只需判断是否被这些可见的圆盘盖住就可以了. 但是划分独立圆弧时还是需要用所有圆来划分, 包括上面的可见的不可见的以及下面的, 这是因为跟后面有关...

         那么第二类咋办呢? 第二类用上面第一类的方法没法处理. 这里就需要书上教的方法了(我也不知道怎么想出来的...). 对于某段可见的独立圆弧, 它下方第一个圆盘是可见的. 而且, 一个圆盘第二类可见当且仅当它是某个可见独立圆弧的下方第一个圆盘. 这个不难给出(不太严谨的)证明. 一方面, 如果一个圆盘第二类可见, 那么它的可见区域边界一定是若干条独立圆弧. 任取其中一条, 这个独立圆弧必然是可见的(因为是可见区域的边界), 而且这个独立圆弧到当前圆盘之间不可能有其它圆盘(否则其它圆盘会包含这个独立圆弧, 因而其它圆盘的边界会把当前圆盘的可见区域与这个独立圆弧分隔开). 另一方面, 如果一个圆盘是某个可见独立圆弧下方第一个, 则该圆弧的一个小邻域必然是可见的(圆不能密铺, 而且题目里也说了微小扰动下答案不变), 而这个小邻域包含在当前圆盘内, 所以当前圆盘可见.

         所以, 处理第二类可见的时候, 可以这样做: 用一个bool数组prevOK表示各个圆盘是否被上面的某个圆盘钦定为第二类可见. 从上往下遍历时, 对于每个圆的每条可见独立圆弧的中点(中点和整条弧是等价的), 遍历下方各圆盘, 找到第一个包含该中点的, 将其prevOK置为true即可. 最后判断每个圆盘是否可见时, 条件是第一类可见 or prevOK. 这里可以解释为什么划分独立圆弧要用所有圆来划分了. 不然的话, 可能一条圆弧横跨一个第二类可见的圆的内外, 然后取中点时取到外面去了, 就把这个第二类可见的圆给漏掉了.

         细节处理上, 为了方便求弧的中点, 可以用圆上的极角来刻画这个圆的各个分点, 然后排序之后依次求相邻两项均值即可. 为了处理头和尾相邻, 可以把最小的极角(序列的头)加上2*pi放到最后(序列的尾的后面), 然后就可以一样地取平均值处理了.


#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
#define eps 1e-13

#define PI 3.14159265358979323846

using namespace std;

struct Point
{
	double x,y;
	Point(double _x, double _y):x(_x), y(_y)
	{}
	Point()
	{}
};

typedef Point Vector;

struct Circle
{
	Point c;
	double r;
	Circle(Point _c, double _r):c(_c), r(_r)
	{};
	Circle()
	{}
	
	Point getPoint(double a)
	{
		return Point(c.x + r * cos(a), c.y + r * sin(a));
	}
};

// operators
Vector operator+(Vector a, Vector b)
{
	return Vector(a.x + b.x, a.y + b.y);
}
Vector operator-(Vector a, Vector b)
{
	return Vector(a.x - b.x, a.y - b.y);
}
Vector operator*(Vector a, double k)
{
	return Vector(a.x * k, a.y * k);
}
Vector operator*(double k, Vector a)
{
	return Vector(a.x * k, a.y * k);
}
Vector operator/(Vector a, double k)
{
	return Vector(a.x / k, a.y / k);
}
bool operator<(Point a, Point b)
{
	if(fabs(a.x - b.x) > eps)	return a.x < b.x;
	return a.y < b.y;
}

// functions

double PolarAngle(Vector v)
{
	return atan2(v.y, v.x);
}

double Dot(Vector a, Vector b)
{
	return a.x * b.x + a.y * b.y;
}

double Length(Vector v)
{
	return sqrt(Dot(v, v));
}

// get the central polar angle of intersect points in C1
// regard tangent as disjoint
int CCIntersect(Circle C1, Circle C2, vector<double>& ans)
{
	Vector C1C2 = C2.c - C1.c;
	double d = Length(C1C2);
	double r1 = C1.r;
	double r2 = C2.r;
	
	if(d > r1 + r2 || d < fabs(r1 - r2))
	{
		return 0;
	}
	else
	{	
		double t = acos( (d * d + r1 * r1 - r2 * r2) / (2 * d * r1) );
		double ang = PolarAngle(C1C2);
		double t1 = ang + t;
		if(t1 < -PI)	t1 += PI;
		if(t1 > PI)	t1 -= PI;
		double t2 = ang - t;
		if(t2 < -PI)	t2 += PI;
		if(t2 > PI)	t2 -= PI;
		ans.push_back(t1);
		ans.push_back(t2);
		return 2;
	}
}

int N;
Circle circle[107];
vector<Circle> visibleC;
vector<double> sectA;
bool prevOK[107];

int main()
{	
	int N;
	while(scanf("%d", &N), N > 0)
	{
		visibleC.clear();
		memset(prevOK, 0, sizeof(prevOK));
		
		for(int i = 0; i < N; i++)
			scanf("%lf%lf%lf", &circle[i].c.x, &circle[i].c.y, &circle[i].r);
		
		for(int i = N-1; i >= 0; i--)		//each circle
		{
			sectA.clear();
			for(int j = 0; j < N; j++)	//each other circle
			{
				if(j == i)	continue;
				CCIntersect(circle[i], circle[j], sectA);
			}
			
			if(sectA.size() == 0)
			{
				sectA.push_back(0.0);	//no intersection: use any point
				sectA.push_back(0.0);
			}
			else
			{
				sort(sectA.begin(), sectA.end());
				double minA = sectA[0];
				sectA.push_back(minA + 2 * PI);	//meet last and first
			}
			
			bool visible = false;
			for(int j = 0; j < sectA.size() - 1; j++)	//each intersect point
			{
				double ang = (sectA[j] + sectA[j+1]) / 2;
				Point p = circle[i].getPoint(ang);
				bool block = false;
				for(int k = 0; k < visibleC.size(); k++)	//each upper visible circle
				{
					Circle curC = visibleC[k];
					if(Length(curC.c - p) < curC.r)
					{
						block = true;
						break;
					}
				}
				
				if(!block)		//P is visible
				{
					visible = true;
					for(int k = i-1; k >= 0; k--)	//the first lower circle contains P is visible
					{
						Circle curC = circle[k];
						if(Length(curC.c - p) < curC.r)
						{
							prevOK[k] = true;
							break;
						}
					}
				}
			}
			
			if(visible || prevOK[i])
			{
				visibleC.push_back(circle[i]);
			}
		}
		printf("%d\n", visibleC.size());
	}
	
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值