链接:http://poj.org/problem?id=2932
题意:
有n个圆,给定每个圆的圆心和半径,求所有最外层的,即不包含在其他圆内部的圆。
(任意两圆都没有公共点)
思路:
由于两两没有公共点,所以如果圆不在其他圆内部,那么这个圆的圆心就不可以在任何一个圆的内部。很直观的就
O(n2)
的算法。这肯定是过不了的,学习一个新的方法。平面扫描法。
平面扫描:
将扫描线在平面上按照给定的轨迹进行移动,同时不断根据扫描线扫过部分更新信息,从而得到整体所要求的结果。
做法:
考虑两个临界的条件,1)扫描到某个圆的左端,2)扫描到某个圆的右端的时候
1)扫到左端时。判断当前圆是否包含在其他圆中:从当前与扫描线相交的最外层的圆中,找到上下y坐标方向距离当前圆的圆心最近的两个圆。之所以这样是对的,是因为假设不对的话,就是包含或者是相交,这都与题意违背。
2)扫到右端时。将这个圆从与扫描线相交的最外层圆的集合中删除。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <set>
#include <map>
using namespace std;
#define M 40009
#define EPS 1e-9
typedef pair<double,int> P;
int n;
struct node
{
double x,y,r;
};
node nod[M];
bool inside(int i,int j) // 判断圆i 是否在 圆j 的内部
{
double dx = nod[i].x - nod[j].x;
double dy = nod[i].y - nod[j].y;
if(dx * dx + dy * dy - nod[j].r * nod[j].r <= EPS) return true;
return false;
}
void slove()
{
vector<P> events;
for(int i = 0;i < n;i++)
{
events.push_back(P(nod[i].x - nod[i].r,i)); //记录左端
events.push_back(P(nod[i].x + nod[i].r,i+n)); //记录右端
}
sort(events.begin(),events.end());
set<P> outer; //记录与扫描线相交的最外层的圆的集合
vector<int> ans; //确定的最外层的圆
for(int i = 0;i < events.size();i++)
{
int id = events[i].second % n;
if(events[i].second < n) //扫描到左端
{
set<P>::iterator it = outer.lower_bound(P(nod[id].y,id));//二分查找 y坐标大于等于当前圆心y的
if(it != outer.end() && inside(id,it->second)) continue; //上方的
if(it != outer.begin() && inside(id,(--it)->second)) continue; //下方的
ans.push_back(id); //加入最外层的圆
outer.insert(P(nod[id].y,id));
}
else outer.erase(P(nod[id].y,id)); //扫描到右端
}
sort(ans.begin(),ans.end());
printf("%d\n",ans.size());
for(int i = 0;i < ans.size();i++) printf("%d%c",ans[i]+1,i + 1 == ans.size() ? '\n' : ' ');
}
int main()
{
while(scanf("%d",&n) == 1)
{
for(int i = 0;i < n;i++)
{
scanf("%lf %lf %lf",&nod[i].r,&nod[i].x,&nod[i].y);
}
slove();
}
return 0;
}