P1001 · OFFICIAL SOLUTION

P1001 风眼定位 官方题解

Gioush OJ · P1001 风眼定位

风眼定位

【题意简述】

存在一个长度为 nn、元素互不相同的隐藏序列。调用 Query(l,r)\operatorname{Query}(l,r) 可以得到区间 [l,r][l,r] 中次大元素在原序列中的位置。要求在不超过 4040 次询问内返回全局最大元素的位置。

【Hint】

【提示】

先询问整个区间得到全局次大值的位置 ss。只要后续询问区间同时包含 ss 与全局最大值,返回值就一定仍为 ss

【数据点 111】

n20n\leq 20 时,先询问整个区间得到全局次大值位置 ss,再逐步缩短包含 ss 的区间,排除不可能成为最大值的位置。即使线性缩短区间,询问次数也不会超过 2020

这一档已经给出最重要的判断:若某个询问区间包含 ss,那么返回 ss 当且仅当这个区间也包含全局最大值。

【数据点 222】

特殊性质保证最大值位于端点。询问 [1,n][1,n] 得到 ss 后,最大值只可能在 11nn。若询问 [1,s][1,s] 返回 ss,说明最大值在左侧,答案为 11;否则答案为 nn

【正解】

首先询问 [1,n][1,n],记返回位置为 ss。若 s=1s=1,最大值只能位于右侧;若 s=ns=n,最大值只能位于左侧。

1<s<n1<s<n 时,询问 [1,s][1,s]。若返回 ss,则区间中同时包含全局最大值与全局次大值,最大值位于 ss 左侧;否则最大值位于 ss 右侧。

接下来只需要在已经确定的一侧二分,并保证每次询问都包含 ss

【最大值位于左侧】

维护答案属于 [l,r][1,s1][l,r]\subseteq[1,s-1]。取上中点

m=l+r+12,m=\left\lfloor\dfrac{l+r+1}{2}\right\rfloor,

并询问 [m,s][m,s]

  • 若返回 ss,说明最大值位于 [m,s1][m,s-1],令 l=ml=m
  • 否则最大值位于 [l,m1][l,m-1],令 r=m1r=m-1

【最大值位于右侧】

维护答案属于 [l,r][s+1,n][l,r]\subseteq[s+1,n]。取下中点

m=l+r2,m=\left\lfloor\dfrac{l+r}{2}\right\rfloor,

并询问 [s,m][s,m]

  • 若返回 ss,说明最大值位于 [s+1,m][s+1,m],令 r=mr=m
  • 否则最大值位于 [m+1,r][m+1,r],令 l=m+1l=m+1

两种二分都始终保留真实答案,并严格缩短区间,所以最终 l=rl=r 时得到全局最大值位置。

【询问次数】

第一次询问得到 ss,至多再用一次询问判断方向。二分长度不超过 n1n-1,所以总询问次数不超过

2+log2(n1).2+\left\lceil\log_2(n-1)\right\rceil.

n105n\leq 10^5 时,这个数量远小于 4040。额外空间复杂度为 O(1)\mathcal{O}(1)

【参考代码】

/*Author:EhundateghDate:2026/7/30Name:locate.cppYou steal,I kill.*/#include "locate.h"using namespace std; void Init(int c,int T){    return;} int Locate(int c,int n){    int Second=Query(1,n);    bool Left;    if(Second==1) Left=false;    else if(Second==n) Left=true;    else Left=(Query(1,Second)==Second);    if(Left){        int l=1,r=Second-1;        while(l<r){            int Mid=(l+r+1)>>1;            if(Query(Mid,Second)==Second) l=Mid;            else r=Mid-1;        }        return l;    }    int l=Second+1,r=n;    while(l<r){        int Mid=(l+r)>>1;        if(Query(Second,Mid)==Second) r=Mid;        else l=Mid+1;    }    return l;}