Central Europe Regional Contest 2014 B [Gym - 100543B]

题目
这题题意也很简单,就是在某个山坡pi,pi+1上沿着山坡往右往前看过去最先看到的是哪个山坡,题目按顺序给出了p1~pn个坐标。
当时做的时候不知道要怎么快速查询到那个第一个与直线pi,pi+1相交的山坡线段。因为山坡形状很不规则。没有规律来进行诸如二分啊,线段树这些可以logn时间的操作。
大佬题解
参考了一下大佬的题解,就跟他说的那样有道理,如果用上凸包,就可以把不规则的线段弄成一个凸包(只要上半部分,就是往上凸的那部分点),然后可以通过找规律发现快速二分判定凸包上有没有一段边是和直线pi,pi+1相交的。但是这个二分我觉得才是整个程序的灵魂所在,不过还是第一次学习了在线段树上做其他东西的方法,线段树有个很重要的性质,空间顶多是原来的四倍,利用这个性质可以准确估算空间复杂度来让这样的方法变得可行

/*************************************************************************
    > Author: MentalOmega
    > Mail: 965194745@qq.com
    > Created Time: 2017年7月22日
    > function:凸包和线段树的巧妙结合
 ************************************************************************/
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5+10;
struct Point
{
    int x,y;
    friend Point operator + (const Point &a,const Point &b)
    {
        return {a.x+b.x,a.y+b.y};
    }
    friend Point operator - (const Point &a,const Point &b)
    {
        return {a.x-b.x,a.y-b.y};
    }
    friend long long operator ^ (const Point &a,const Point &b)
    {
        return (long long)a.x*b.y-(long long)b.x*a.y;
    }
};
int n;
#define lson rt<<1,l,l+r>>1
#define rson rt<<1|1,l+r>>1,r
void build(vector<vector<Point>> &T,int rt,int l,int r,const vector<Point> &P)
{
    if(l>=r) return ;//不合法停止构建
    vector<Point> &t = T[rt];
    for(int i=l;i<=r;i++)//基于平面扫描法的Graham扫描算法
    {
        //这题只需要构造凸包的上半部分
        while(t.size()>1&&((P[i]-t[t.size()-2])^(t[t.size()-1]-t[t.size()-2]))<=0) t.pop_back();
        t.push_back(P[i]);
    }
    if(l+2<=r)
        build(T,lson,P),build(T,rson,P);
}
bool cut(Point p,Point q,vector<Point> &t)
{
    int l=0,r=t.size()-1;//二分找直线跟线段的交点
    for(int i=0;i<50;i++)
    {
        int m=l+r>>1;
        if(((t[m]-p)^(q-p))<((t[m+1]-p)^(q-p))) r=m;
        else l=m;
    }
    return ((t[l]-p)^(q-p))<0||((t[l+1]-p)^(q-p))<0;//这个二分的条件真的确实奇妙,要慎重考虑这里
}
bool query(vector<vector<Point> > &T,int rt,int l,int r,Point p,Point q,int b,int e,int &ans)//在b~e区间中找寻一个山坡的线段与直线pq的交点
{
    if(e<=l||b>=r) return false;
    if(b<=l&&e>=r)
    {
        if(!cut(p,q,T[rt])) return false;
        if(l+1==r)
        {
            ans=l;
            return true;
        }
    }
    return query(T,lson,p,q,b,e,ans)||query(T,rson,p,q,b,e,ans);//先找左儿子再找右儿子就能找到最先看到的山坡
}
int main()
{
    if (fopen("in.txt", "r") != NULL)
    {
        freopen("in.txt", "r", stdin);
        // freopen("out.txt", "w", stdout);
    }
    int t;
    cin>>t;
    while(t--)
    {
        scanf("%d",&n);
        vector<Point>P(n);//学习一下c++工程的正宗写法
        for(auto &p : P) scanf("%d%d",&p.x,&p.y);
        vector<vector<Point>> T(n<<2);//线段树的构造,每个线段树的结点都放着l~r区间的点构成的凸包的上半部分的点
        build(T,1,0,n-1,P);
        for(int i=0;i<n-2;i++)
        {
            int ans=-1;
            query(T,1,0,n-1,P[i],P[i+1],i+1,n-1,ans);//只往前方寻找
            printf("%d ",ans+1);
        }
        puts("0");
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值