- 给定二维平面内一条由 \(n\) 个折点构成的折线,保证折点横坐标递增。
- 对于每条折线段 \(P_iP_{i+1}\),找到大于 \(i\) 的最小的 \(j\) ,满足 \(P_jP_{j+1}\) 上存在一点严格在射线 \(P_iP_{i+1}\) 上方。
- \(2\le n\le10^5\)
二分答案
容易想到二分答案,这样每次就只需检验某个区间内所有点是否都在 \(P_iP_{i+1}\) 下方。
假设当前的答案区间是 \([l,r]\),如果把 \(P_i,P_{i+1}\) 和 \(P_l,P_{l+1},\cdots,P_{r+1}\) 放在一起,则所有点都在 \(P_iP_{i+1}\) 下方充要于 \(P_iP_{i+1}\) 在 上凸壳 中。
而 \(P_iP_{i+1}\) 在上凸壳中,充要于除去 \(P_i,P_{i+1}\) 之后,剩下的 \(P_l,P_{l+1},\cdots,P_{r+1}\) 这些点构成的上凸壳在 \(P_iP_{i+1}\) 下方。
线段树维护凸壳
因为点是不变的,我们可以对于线段树上每个区间 \([l,r]\),预处理出 \(P_l,P_{l+1},\cdots,P_{r+1}\) 构成的上凸壳。
然后二分就可以转化成线段树上二分,每次三分一下,看看这个区间中的点构成的上凸壳中是否有点在当前询问的 \(P_iP_{i+1}\) 上方即可。
代码:\(O(n\log^2n)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 100000
using namespace std;
int n;struct P
{
int x,y;I P(CI a=0,CI b=0):x(a),y(b){}
I P operator + (Cn P& o) Cn {return P(x+o.x,y+o.y);}I P operator - (Cn P& o) Cn {return P(x-o.x,y-o.y);}
I long long operator ^ (Cn P& o) Cn {return 1LL*x*o.y-1LL*y*o.x;}
}p[N+5];
namespace FastIO
{
#define FS 100000
#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
int OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
Tp I void write(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc(' ');}I void ed() {pc('0'),pc('\n');}
}using namespace FastIO;
class SegmentTree
{
private:
#define PT CI l=1,CI r=n-1,CI rt=1
#define LT l,mid,rt<<1
#define RT mid+1,r,rt<<1|1
int ct[N<<2];vector<P> G[N<<2];
public:
I void Bd(PT)//预处理
{
RI& T=ct[rt];vector<P>& g=G[rt];T=-1,g.clear();
for(RI i=l;i<=r+1;++i) {W(T>0&&((p[i]-g[T])^(g[T]-g[T-1]))<=0) --T,g.pop_back();++T,g.push_back(p[i]);}//[l,r+1]中的点构成的上凸壳
if(l==r) return;RI mid=l+r>>1;Bd(LT),Bd(RT);
}
I int Q(CI x,PT)//线段树上二分
{
RI mid=l+r>>1,L=0,R=ct[rt],M1,M2;long long t1,t2;if(x>=mid) return Q(x,RT);//若x+1在右区间,直接去右区间
W(R-L>2) t1=(p[x+1]-p[x])^(G[rt][M1=L+(R-L)/3]-p[x]),t2=(p[x+1]-p[x])^(G[rt][M2=M1+(R-L)/3]-p[x]),t1>t2?R=M2:L=M1;//三分是否有点在当前线段上方
RI i;for(i=L;i<=R;++i) if(((p[x+1]-p[x])^(G[rt][i]-p[x]))>0) break;if(i>R) return 0;//如果没有返回0
if(l==r) return l;RI t=Q(x,LT);return t?t:Q(x,RT);//优先尝试左子树,失败则去右子树(因为找不到会直接返回,复杂度正确)
}
}S;
int main()
{
RI Tt,i;read(Tt);W(Tt--) {for(read(n),i=1;i<=n;++i) read(p[i].x,p[i].y);S.Bd();for(i=1;i<n-1;++i) write(S.Q(i));ed();}return clear(),0;
}