题意:
一个排列,1e5的范围,至多20次询问,每次可以询问一个子段的次大值位置。求最大值的下标位置。
思路:
通过第一次询问找出次大值位置 p o s pos pos,然后询问 ( 1 , p o s ) (1,pos) (1,pos)确定,如果结果等于 p o s pos pos,那么最大值是在 p o s pos pos左边,否则在 p o s pos pos右边。
这样就得到了一个确定了最大值所在的区间,且这个区间左边界(或右边界)为次大值。
对于左边界为次大值的情况,假设最大值位置为 x x x,那么对于 i < x i<x i<x的所有的询问 ( p o s , i ) (pos,i) (pos,i)结果都不为 p o s pos pos,对于 i ≥ x i≥x i≥x的所有询问结果都为 p o s pos pos。这就可以二分确定 x x x的位置。
右边界为次大值的情况同理。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#include <vector>
#include <iostream>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 7;
const int mod = 1e9 + 7;
typedef long long ll;
int query(int l,int r) {printf("? %d %d\n",l,r);fflush(stdout);int ans;scanf("%d",&ans);return ans;
}
int solve(int n) {int x = query(1,n);bool flag = true; //次大值是否在最左边if(x != 1 && query(1,x) == x) {flag = false;}int ans = 0;if(flag) {int l = x + 1,r = n;ans = l;while(l <= r) {int mid = (l + r) >> 1;if(query(x,mid) == x) {ans = mid;r = mid - 1;} else {l = mid + 1;}}} else {int l = 1,r = x - 1;ans = l;while(l <= r) {int mid = (l + r) >> 1;if(query(mid,x) == x) {ans = mid;l = mid + 1;} else {r = mid - 1;}}}return ans;
}
int main() {int n;scanf("%d",&n);printf("! %d\n",solve(n));return 0;
}