计算几何进阶-扫描线

关于扫描线的题目,做法我感觉应该比较容易看出来,但是其中的性质很难找。只要结合题目的背景来找出它其中的数学性质,题目就能迎刃而解了。
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题实际上我还没有想清楚,刚刚写的代码还没来得及执行电脑就蓝屏了。。。等想清楚了会更新该博客的。

猜你喜欢

转载自blog.csdn.net/qq_34921856/article/details/80803148