这题题意也很简单,就是在某个山坡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;
}