转自http://hi.baidu.com/convexhull/blog/item/9995950088b44c4ef919b8a0.html
问题描述:
给定平面上n个外切和相离的圆,每圆都不包含坐标原点且过原点做每个圆的切线,任意两个切线之间的夹角不小于1e-6。
现在从坐标原点发出射线,求至少有多少条射线使得每个圆都被一条射线穿过。注:(0 <= n <= 1000)。
问题分析:
如下图,给定的三个圆可以被一条射线同时穿过。每个圆在原点处开来等价为一个边界为过原点做圆的两条切线的区间。
这个区间可以用角度来表示,这样问题就进一步转化成了在n个区间上有至少取多少个不同的点使得每个区间都含有一个点。
由于在平面坐标系中的角度是周期性的,具有周期性的区间问题难点就在于循环节处。就本题而言,如果你用函数atan2()
来处理的话,由于atan2()返回的函数值delta局限于:-PI < delta <= PI ,所以如果圆的两切线的对于的角度区间如果
跨过了x负半轴,就需要特别处理了,需要将角度统一到 ( -PI , PI ] 的范围内。
有了上面的预处理,接下来要解决的就是如何找最少的点。怎么找呢?贪心!为了便于描述解题思路,这里介绍线段区间
上同样问题的解法,类比,可以解决上述问题。如下图:
每条线段区间(Interval),编号(1-5)是按照右端点升序编制。怎么贪心呢?因为每个Interval上都必须有一个点,
所以排序后,对于Interval 1,必须要选一个点落于其上,贪心地选取点右端点J,这样点J落在上面的区间有:Interval 1,
Interval 2,Interval 3。同样的贪心策略,找到第一个J未落于其上的区间Interval 4,由于点B可以落在区间:interval 4
,Interval 5, 所以,可以选取点J和点B满足每个Interval都有一个点(B或者J)落于其上。
这里,贪心是正确的。但是,如果死板地用到上面的周期性区间就是错误的。都与周期性区间问题,排序之后还需要
枚举起点。这也正是两种区间问题处理上的不同之处。
MY CODE
#include <stdio.h> #include <math.h> #include <algorithm> using namespace std; const int N = 1001; const double PI = acos(-1), EPS = 1e-8; struct Angle { double l,r; }; bool cmp(const Angle &a,const Angle &b) { return a.l < b.l; } int n; Angle A[N]; int ok(double l,double r,double x) { if(l - r < EPS) return l - x < EPS && x - r < EPS; return l - x < EPS || x - r < EPS ;///穿过-x轴的情况 } int main() { int i,t; double a,b,r; double x,y; scanf("%d",&t); while(t--) { scanf("%d",&n); for(i = 0; i < n; ++i) { scanf("%lf%lf%lf",&a,&b,&r); double dis = sqrt(a * a + b * b); double si = asin(r/dis); double ta = atan2(b,a); A[i].l = ta - si; if(A[i].l < -PI) A[i].l += 2 * PI; A[i].r = ta + si; if(A[i].r > PI) A[i].r -= 2 * PI; //A[i] = Angle(x,y); } sort(A, A + n, cmp); int res = n,tem; for(i = 0; i < n;++i) { tem = 1; double L = A[i].l,R = A[i].r; for(int j = 1;j < n; ++j) { int k = (i + j) % n; if(!ok(L,R,A[k].l)) { L = A[k].l; R = A[k].r; ++ tem ; } else if(ok(L,R,A[k].r))///更新右端点 { R = A[k].r; } } if(tem < res) res = tem; } printf("%d\n",res); } return 0; }