BZOJ4814[Cqoi2017]小Q的草稿

题目

BZOJ4814[Cqoi2017]小Q的草稿

题解

BZOJ这题通过率好像特低。。不造为啥。可能是数据不好造+容易写错。

我也是写了大半天迷迷糊糊的,找了篇题解思路才清晰起来。

首先要注意到这么一点,因为三角形不相交,那如果把三角形拆成三条线段,则不经过三角形区域即为关键点连线不与任何一条线段相交。

看到这题就应该很自然的联想到,枚举一个点然后极角排序然后维护一个啥。
很快我们发现,线段间的关系是不变的,大概意思就是,每条线段会遮盖一个极角区域,如果线段a在某个区域盖住了线段b(极角区域),线段b就不可能在某个区域盖住线段a。除非a消失,否则b一直被盖着。

画画图很好看出来的qw。不过这个实现特别蛋疼。

因为我写的计算几何题特别少。写了好久好久。最后还是懵逼。

我们需要发现这个事情,每个三角形只有一条边是有用的。起初我以为是一条或者两条,后来我发现。。点不可能在三角形内部,所以本质上还是只要一条,就是极角夹角(我不造是不是该这么叫。。可能看代码更好懂)最大的那两个点构成的那条边。

然后问题就简化了。只有线段和点。就打加线段标记,删除线段标记,然后那一堆按极角排序,用个set维护一下线段间的关系就好了。

一个小技巧就是,之前我一直被跨越x负半轴的线段所困扰,因为那两个极角、、、很蛋疼。但是某题解的作者用了一种非常棒的方法qwq。就是找出跟x轴负半轴的交点,然后拆成两条线段。这个方法真的解决了无数的困扰!!!!!!!在此特别向像我一样的计算几何萌新一定要记住这个方法!!!!因为枚举+扫描线+数据结构的题太多辣,这个思路也太妙啦。(dalao求不嘲讽萌新qwq)

个人觉得这道题考代码实现能力。比如说set里的小于号的定义要开一个全局变量。你得明白哪些set的操作要用小于号,否则会GG。

啊非常感谢某题解作者qwq,代码写的非常棒。
http://blog.csdn.net/u013849646/article/details/70197393

代码

//QWsin
#include<set>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=2000+10;

const double eps=1e-9;
const double PI=acos(-1.0);

inline int dcmp(const double &x){
    return x<-eps?-1:(x>eps ? 1:0);
}

struct point{
    double x,y,ang;
    point(){x=y=0;} 
    point (double x,double y):x(x),y(y){}
    inline void getang(){ang=atan2(y,x);}
    inline void input(){scanf("%lf%lf",&x,&y);}
    inline bool operator < (const point &rhs)const{
        return dcmp(ang-rhs.ang)<0;
    }
}p[maxn],pp[maxn],tr[maxn][5],tt[maxn][5];

typedef point vc;
inline vc operator - (const point &a,const point &b){return vc(a.x-b.x,a.y-b.y);}
inline vc operator + (const point &a,const vc &b){return vc(a.x+b.x,a.y+b.y);}
inline vc operator * (const vc &v,const double &k){return vc(v.x*k,v.y*k);}
inline vc operator / (const vc &v,const double &k){return vc(v.x/k,v.y/k);}
inline double dot(const vc &a,const vc &b){return a.x*b.x+a.y*b.y;}
inline double cross(const vc &a,const vc &b){return a.x*b.y-a.y*b.x;}
inline double getl(const vc &v){return (v.x*v.x+v.y*v.y);}
inline double Ang(const point &a,const point &b){return (dot(a,b)/sqrt(getl(a))/sqrt(getl(b)));}
inline vc jiao(const point &p1,const vc &v1,const point &p2,const vc &v2){
    vc u=p1-p2;
    double t=cross(v2,u)/cross(v1,v2);
    return p1+v1*t; 
}

int n,m;
inline void init_data()
{
    cin>>n>>m;
    for(int i=1;i<=n;++i) p[i].input();
    for(int i=1;i<=m;++i)
        for(int j=0;j<3;++j)
            tr[i][j].input();
}

const point O=point(0,0);
vc base;//扫描线当前的位置 
struct Line{
    point x,y;
    double ang;
    int k,id;
    Line(){x=y=point();ang=0;k=0;}
    Line(point x,point y,double ang,int k,int id):x(x),y(y),ang(ang),k(k),id(id){}
    inline bool operator < (const Line &rhs)const{
        double l1=getl(jiao(O,base,x,y-x));
        double l2=getl(jiao(O,base,rhs.x,rhs.y-rhs.x));
        return l1<l2;
    }
}L[maxn*2];

set<Line>st;
set<Line>::iterator it,iit[maxn];

inline int cmp(const Line &a,const Line &b){
    return dcmp(a.ang-b.ang) < 0 || (dcmp(a.ang-b.ang)==0&&a.k > b.k);  
}

int tot;

inline void prework(int chose)
{
    point P=p[chose];

    for(int i=1,_=0;i<=n;++i) if(i!=chose) {
        pp[++_]=p[i]-P;pp[_].getang();
    }

    sort(pp+1,pp+n);

    tot=0;int id_cnt=0;
    for(int i=1;i<=m;++i)
    {
        double mx=1e6;
        for(int j=0;j<3;++j) tt[i][j]=tr[i][j]-P;
        tt[i][3]=tt[i][0];

        point u,v;

        for(int j=0;j<3;++j)
        {
            //因为点不会在三角形内部所以只有一条边有用(横跨最大的那条边)
            double ang=Ang(tt[i][j],tt[i][j+1]);//这里Ang没用acos,因为这个角度肯定小于180,减少acos调用次数 
            if(ang < mx){mx=ang;u=tt[i][j],v=tt[i][j+1];}
        }

        u.getang();v.getang();
        if(u.ang > v.ang) swap(u,v);
        if(v.ang - u.ang < PI) {
            ++id_cnt;
            L[++tot]=Line(u,v,u.ang,1,id_cnt);
            L[++tot]=Line(u,v,v.ang,0,id_cnt);
        }
        else//过交界拆成两条线段,一个非常重要的技巧!!! 
        {
            point tmp=jiao(u,v-u,O,point(-1,0));
            tmp.ang=dcmp(u.ang)==1?PI:-PI;
            ++id_cnt;
            L[++tot]=Line(tmp,u,tmp.ang,1,id_cnt);
            L[++tot]=Line(u,tmp,u.ang,0,id_cnt);
            tmp.ang=dcmp(v.ang)==1?PI:-PI;
            ++id_cnt;
            L[++tot]=Line(v,tmp,v.ang,1,id_cnt);
            L[++tot]=Line(tmp,v,tmp.ang,0,id_cnt);
        }
    }

    sort(L+1,L+tot+1,cmp);
}

inline int solve(int id)
{
    base=vc(0,0);
    if(id!=1)st.clear();
    int j=1,ans=0;
    for(int i=1;i<n;++i)
    {
        while(j<=tot && (dcmp(L[j].ang-pp[i].ang) < 0 ||(dcmp(L[j].ang-pp[i].ang)==0&&L[j].k))){
            base=L[j].x;//earse的时候也会用到base!!    
            if(L[j].k==0) 
                st.erase(iit[L[j].id]);
            else{
                iit[L[j].id]=st.insert(L[j]).first;
            }
            ++j;
        }
        if(st.size()==0) {++ans;continue;}
        base=p[i];
        it=st.begin();

        double l1=getl(pp[i]);
        double l2=getl(jiao(O,pp[i],it->x,it->y - it->x));

        ans+=(dcmp(l1 - l2)<0);
    }
//  printf("%d %d\n",id,st.size());

    return ans;
}

int main()
{
    init_data();

    int ans=0;
    for(int i=1;i<=n;++i)
    {
        prework(i);
        ans+=solve(i);
    }
    cout<<ans/2;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值