先考虑怎么对一组询问求解答案。容易想到一种贪心策略:如果 \(a_{q_i}<a_i\),那么我们肯定希望自己能够尽量快地下落,因此如果遇到一个下落速度大于当前世界下落速度的传送门我们肯定就会进那个世界,如此走下去知道能够传送到世界 \(q_i\) 为止。对于 \(a_{q_i}>a_i\) 的情况也类似,只不过要下落速度越慢越好。
考虑处理前一种情况,后一种情况则是镜像的。我们将 \(a_j>a_i\) 的部分插入直线凸包,那么从 \(i\) 开始经过的世界就是插入 \(i\) 这条直线时,\(i\) 这条直线右边的部分,维护出直线凸包以后相当于我们要找到直线凸包上与 \(y=a_{q_i}-q_ix\) 有交的那一段,可以在凸包上二分即可,这样时间可以使用追及问题的方法求出。
时间复杂度 \(O(n\log n)\)。
const int MAXN=2e5;
int n,a[MAXN+5],q[MAXN+5],ord[MAXN+5],ans1[MAXN+5],ans2[MAXN+5],stk[MAXN+5],tp;
bool check(int x,int y,int k){return 1ll*(a[x]-a[k])*(y-k)<1ll*(a[y]-a[k])*(x-k);}
void ins(int x,int flg){
while((tp&&((stk[tp]<x)^flg))||(tp>1&&check(stk[tp-1],stk[tp],x)))--tp;
stk[++tp]=x;
if((a[q[x]]<a[x])^flg){
if(flg^(q[x]>stk[1]))return;int l=2,r=tp,p=1;
while(l<=r){
int mid=l+r>>1;
if((flg^(q[x]<stk[mid]))&&check(stk[mid],stk[mid-1],q[x]))p=mid,l=mid+1;
else r=mid-1;
}
ans1[x]=abs(a[stk[p]]-a[q[x]]);ans2[x]=abs(stk[p]-q[x]);
int d=__gcd(ans1[x],ans2[x]);ans1[x]/=d;ans2[x]/=d;
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=n;i++)scanf("%d",&q[i]);
for(int i=1;i<=n;i++)ord[i]=i;
sort(ord+1,ord+n+1,[&](int x,int y){return a[x]>a[y];});
for(int i=1;i<=n;i++)ins(ord[i],0);tp=0;
for(int i=n;i;i--)ins(ord[i],1);
for(int i=1;i<=n;i++){
if(!ans1[i])puts("-1");
else printf("%d/%d\n",ans1[i],ans2[i]);
}
return 0;
}
标签:直线,洛谷,int,tp,stk,P6010,USACO20JAN,MAXN,凸包
From: https://www.cnblogs.com/tzcwk/p/luogu-P6010.html