【XSY2501】Mountainous landscape(线段树二分,凸包)
先考虑如何确定对于某个 \(i\) 的答案。
题目的要求是:对于每一个 \(i=1,2,\cdots,n-1\),找一个最小的 \(j\) 使得 \(j>i\) 且线段 \(P_jP_{j+1}\)(含端点)与将射线 \(P_iP_{i+1}\) 略向上平移后所得的射线相交。
这个要求可以转化为:拟一条射线 \(P_iP_{i+1}\),找到最小的 \(j\) 使得 \(P_j\) 在这条射线上方(不含射线),那么 \(P_{j-1}P_j\) 就是我们要找的答案。
原因也很简单,由于 \(j\) 是最小的,所以对于任意的 \(k\in [i+1,j-1]\),都有 \(P_{k}\) 在这条射线下方(含射线),于是就能保证射线穿过线段 \(P_{j-1}P_j\),在 \(P_{j-1}P_j\) 之前不存在射线能穿过的线段。
那么现在问题就变成了:对于每一个 \(i=1,2,\cdots,n-1\),找一个最小的 \(j\) 使得 \(j>i+1\) 且点 \(P_j\) 在 \(P_iP_{i+1}\) 上方(不含射线,下同)。
考虑二分,判断是否存在一个 \(j\leq mid\) 满足条件,那么就需要判断点集 \(\{P_{i+2},P_{i+3},\cdots,P_j\}\) 中是否存在点在射线上方。
看似我们并没有把问题简单化,甚至更加复杂化,因为貌似我们还是要枚举所有的点,甚至还加上了个二分。
但实际上我们能利用一个很好的性质:把二分的判断转化为判断点集 \(\{P_{i+2},P_{i+3},\cdots,P_j\}\) 的上凸壳中是否存在点在射线上方,我们就能通过三分来 \(O(\log n)\) 判断了。
而且一段区间的点的上凸壳是不会随着询问改变而改变的,而且我们可以通过线段树来维护一段区间的点的上凸壳。
于是我们可以直接在线段树上二分:假设我们当前要在区间 \([l,r]\) 寻找最小的 \(j\),那我们用三分判断左儿子内有没有符合条件的 \(j\),有的话就去左儿子找,否则去右儿子找。
总时间复杂度 \(O(Tn\log ^2n)\)。
#include<bits/stdc++.h>
#define N 100010
#define ll long long
using namespace std;
inline int read()
{
int x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<1)+(x<<3)+(ch^'0');
ch=getchar();
}
return x*f;
}
struct Point
{
int x,y;
Point(){};
Point(int xx,int yy){x=xx,y=yy;}
}p[N],sta[N],s,t;
Point operator - (Point a,Point b){return Point(a.x-b.x,a.y-b.y);}
ll cross(Point a,Point b){return 1ll*a.x*b.y-1ll*b.x*a.y;}
int n;
int top;
vector<Point>v[N<<2];
void insert(Point u)
{
while(top>1&&cross(sta[top]-sta[top-1],u-sta[top-1])>=0) top--;
sta[++top]=u;
}
void merge(int k)
{
top=0;
int lc=k<<1,rc=k<<1|1;
int l=0,r=0;
while(l<v[lc].size()&&r<v[rc].size())
{
if(v[lc][l].x<v[rc][r].x) insert(v[lc][l]),l++;
else insert(v[rc][r]),r++;
}
while(l<v[lc].size()) insert(v[lc][l]),l++;
while(r<v[rc].size()) insert(v[rc][r]),r++;
for(int i=1;i<=top;i++) v[k].push_back(sta[i]);
}
void build(int k,int l,int r)
{
v[k].clear();
if(l==r)
{
v[k].push_back(p[l]);
return;
}
int mid=(l+r)>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
merge(k);
}
bool check(int k)
{
int l=0,r=v[k].size()-1,ans=-1;
while(l<=r)
{
int mid=(l+r)>>1;
if(!mid||cross(t-s,v[k][mid-1]-s)<cross(t-s,v[k][mid]-s)) ans=mid,l=mid+1;
else r=mid-1;
}
return cross(t-s,v[k][ans]-s)>0;
}
int query(int k,int l,int r,int x)
{
if(x<=l)
{
if(!check(k)) return 0;
if(l==r) return l-1;
}
int mid=(l+r)>>1;
if(x<=mid)
{
int tmp=query(k<<1,l,mid,x);
if(tmp) return tmp;
}
return query(k<<1|1,mid+1,r,x);
}
int main()
{
int T=read();
while(T--)
{
n=read();
for(int i=1;i<=n;i++)
p[i].x=read(),p[i].y=read();
build(1,1,n);
for(int i=1;i<n-1;i++)
{
s=p[i],t=p[i+1];
printf("%d ",query(1,1,n,i+2));
}
puts("0");
}
return 0;
}