关于扫描线的题目,做法我感觉应该比较容易看出来,但是其中的性质很难找。只要结合题目的背景来找出它其中的数学性质,题目就能迎刃而解了。
A - Coneology
题意:一个笛卡尔坐标系中有许多的圆,有些圆包含其他的圆,一定不存在相交的圆。问有多少个没有被任一个圆包含的圆。
4e4的数据量肯定不允许我们暴力,暴力实际上有许多没必要的计算。因为根据他们的位置关系,有些圆之间不相交能推出另个圆也不和这个圆相交。所以我们得利用他们的位置关系来计算,这里也就是用到了扫描线。
从左往右扫描,左端点进set,右端点出set。并且set是到目前为止右端点还没到的不相交的圆的集合。
碰到左端点的时候,就得将该圆与set里面的圆进行包含判断。而这个判断也并不是与所有set里的圆都得判断一遍,我们只需找到set里面y第一个大于等于它的和y第一个小于它的两个圆,与其判断即可。大家可以画画图想一想。
碰到右端点将set里面的圆删除即可。
所以我们要在set里面存y值及下标。
#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<cstring>
#include<vector>
#include<set>
#include<math.h>
using namespace std;
const int maxn=400005;
const double eps=1e-3;
int sgn(double x)
{
if(fabs(x)<eps)return 0;
if(x>0)return 1;
return -1;
}
double r[maxn],x[maxn],y[maxn];
vector<pair<double,int> >V;
set<pair<double,int> >S;
vector<int>ans;
bool check(int idx1,int idx2)
{
double X=x[idx1]-x[idx2];
double Y=y[idx1]-y[idx2];
double dis=sqrt(X*X+Y*Y);
return sgn(dis-r[idx1]-r[idx2])>=0;
}
int main()
{
int N;
scanf("%d",&N);
for(int i=0; i<N; i++)
{
scanf("%lf %lf %lf",&r[i],&x[i],&y[i]);
V.push_back(make_pair(x[i]-r[i],i));
V.push_back(make_pair(x[i]+r[i],i+N));
}
sort(V.begin(),V.end());
for(int i=0; i<V.size(); i++)
{
int idx=V[i].second%N;
if(V[i].second>=N)
{
S.erase(make_pair(y[idx],idx));
}
else
{
set<pair<double,int> >::iterator tidx=S.lower_bound(make_pair(y[idx],idx));
if(tidx!=S.end())
if(!check(tidx->second,idx))
continue;
if(tidx!=S.begin())
if(!check((--tidx)->second,idx))
continue;
S.insert(make_pair(y[idx],idx));
ans.push_back(idx);
}
}
sort(ans.begin(),ans.end());
printf("%d\n",ans.size());
for(int i=0; i<ans.size(); i++)
{
if(i)printf(" ");
printf("%d",ans[i]+1);
}
puts("");
return 0;
}
B - Moonmist
求最小圆距。
直接讲思路吧,画了半天图总感觉怪怪的。。
求这个圆距,我们可以二分距离num,然后判断是否有圆相交。
判断是否有圆相交可以用两条扫描线,一个枚举左边界i,一个枚举右边界j。
当x[i]-r[i]-num<=x[j]+r[j]+num,则判断i圆并将i圆放入set里,否则将i圆在set里删掉。
这样写可以过题,但是有bug。
大家可以看看这个样例
1
3
-10 20 1
0 0 10
30 40 39
所以我们需要在圆删除之前再判断一次。
#include<bits/stdc++.h>
using namespace std;
const int maxn=50005;
int X[maxn],Y[maxn],R[maxn];
int rL[maxn],rR[maxn],rU[maxn];
bool cmpL(int a,int b)
{
return X[a]-R[a]<X[b]-R[b];
}
bool cmpR(int a,int b)
{
return X[a]+R[a]<X[b]+R[b];
}
bool cmpU(int a,int b)
{
if(Y[a]!=Y[b])return Y[a]<Y[b];
return X[a]<X[b];
}
int urank[maxn];
double dis(int a,int b)
{
double x=X[a]-X[b];
double y=Y[a]-Y[b];
return sqrt(x*x+y*y);
}
double dis2(int a,int b)
{
double x=X[a]-X[b];
double y=Y[a]-Y[b];
return x*x+y*y;
}
set<int>S;
typedef set<int>::iterator it;
bool check2(int num,double add)
{
int itrank=urank[num];
it itor=S.lower_bound(itrank);
if(itor!=S.end())
{
double dist=dis(rU[*itor],num);
if(dist<=R[rU[*itor]]+R[num]+2*add)return 1;
}
if(itor!=S.begin())
{
itor--;
double dist=dis(rU[*itor],num);
if(dist<=R[rU[*itor]]+R[num]+2*add)return 1;
}
return 0;
}
int n;
bool check(double num)
{
S.clear();
int i=0,j=0;
while(i<n||j<n)
{
if(j==n||i<n&&X[rL[i]]-R[rL[i]]-num<=X[rR[j]]+R[rR[j]]+num)
{
if(check2(rL[i],num))
return true;
S.insert(urank[rL[i]]);
i++;
}
else
{
S.erase(urank[rR[j]]);
if(check2(rR[j],num))
return true;
j++;
}
}
return false;
}
void solve()
{
double l=0,r=dis(0,1);
double mid;
for(int t=1;t<=40;t++)
{
mid=(l+r)/2;
if(check(mid))
r=mid;
else l=mid;
}
printf("%.6f\n",mid*2);
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%d %d %d",&X[i],&Y[i],&R[i]);
rL[i]=rR[i]=rU[i]=i;
}
sort(rL,rL+n,cmpL);
sort(rR,rR+n,cmpR);
sort(rU,rU+n,cmpU);
for(int i=0;i<n;i++)
urank[rU[i]]=i;
solve();
}
}
之后会更详细的讲解该题。。
C - Light and Shadow
这题一开始理解错题意了,该题意为能照到的棍子的个数。
由于题目已经说了,不存在两根棍子相交,所以距离点光源最近的棍子不管点光源怎么射,它都是最前面的。所以我们可以以点光源为原点,以极坐标排序,逆时针扫一下。记录一下每条棍子的起点和终点,当遇到棍子的起点时,我们可以以原点和它的起点作一条射线,计算射线与棍子的交点距原点的距离来重新将set里面的棍子排序。
当然这里有一个特别的地方,就是与射线(-1,0)相交的棍子它们的起点在第二象限,按照我们那种排序方式它会在最后才被加进去,所以我们得预先把它装进来。
#include<bits/stdc++.h>
using namespace std;
const int maxn=10005;
const double eps=1e-8;
int sgn(double x)
{
if(fabs(x)<eps)return 0;
if(x>0)return 1;
return -1;
}
struct Point
{
double x,y;
Point(double sx,double sy):x(sx),y(sy){}
Point(){}
Point operator-(const Point &b)const
{
return Point(x-b.x,y-b.y);
}
double operator*(const Point &b)const
{
return x*b.x+y*b.y;
}
double operator^(const Point &b)const
{
return x*b.y-y*b.x;
}
bool operator<(const Point &b)const
{
return x*b.y<y*b.x;
}
void input()
{
scanf("%lf %lf",&x,&y);
}
};
Point cur;
typedef const Point CP;
Point intersection(CP &u1,CP &u2,CP &v1,CP &v2)
{
Point ret=u1;
double t=((u1.x-v1.x)*(v1.y-v2.y)-(u1.y-v1.y)*(v1.x-v2.x))
/((u1.x-u2.x)*(v1.y-v2.y)-(u1.y-u2.y)*(v1.x-v2.x));
ret.x+=(u2.x-u1.x)*t;
ret.y+=(u2.y-u1.y)*t;
return ret;
}
double dis(Point a)
{
return sqrt(a.x*a.x+a.y*a.y);
}
struct line
{
Point u,v;
int idx;
int flag;
double ang;
line(Point _u,Point _v):u(_u),v(_v)
{
ang=atan2(u.y,u.x);
}
line(){}
bool operator<(const line &b)const
{
Point tmpa=intersection(Point(0,0),cur,u,v);
Point tmpb=intersection(Point(0,0),cur,b.u,b.v);
return sgn(dis(tmpa)-dis(tmpb))<0;
}
}Lines[maxn*2];
bool cmp(line a,line b)
{
return a.ang<b.ang;
}
set<line>S;
bool ok[maxn];
bool s_intersection(Point a,Point b)
{
Point tmp=Point(-1,0);
return sgn((a^tmp)*(a^b))>0&&sgn((a^tmp)*(tmp^b))>0;
}
int n;
void input()
{
Point O;
S.clear();
memset(ok,0,sizeof(ok));
O.input();
Point a,b;
for(int i=0;i<n;i++)
{
a.input();
b.input();
a=a-O;
b=b-O;
if(a<b)swap(a,b);
Lines[2*i]=line(a,b);
Lines[2*i].idx=i,Lines[2*i].flag=1;
Lines[2*i+1]=line(b,a);
Lines[2*i+1].idx=i,Lines[2*i+1].flag=0;
}
sort(Lines,Lines+2*n,cmp);
for(int i=0;i<2*n;i++)
{
if(Lines[i].flag==1&&s_intersection(Lines[i].u,Lines[i].v))
{
cur=Lines[i].u;
S.insert(Lines[i]);
}
}
for(int i=0;i<2*n;i++)
{
if(Lines[i].flag==1)
{
cur=Lines[i].u;
S.insert(Lines[i]);
}
else
{
line tmp=Lines[i];
tmp.flag=1;
swap(tmp.u,tmp.v);
tmp.ang=atan2(tmp.u.y,tmp.u.x);
S.erase(tmp);
}
if(!S.empty())
{
set<line>::iterator itor=S.begin();
ok[(*itor).idx]=1;
}
}
int ans=0;
for(int i=0;i<n;i++)if(ok[i])ans++;
printf("%d\n",ans);
}
int main()
{
while(~scanf("%d",&n))
{
input();
}
return 0;
}
B题实际上我还没有想清楚,刚刚写的代码还没来得及执行电脑就蓝屏了。。。等想清楚了会更新该博客的。