C1. Guessing the Greatest (easy version)
C2. Guessing the Greatest (hard version)
Codeforces C题Guessing the Greatest分为easy和hard两个版本,唯一区别在于对询问次数的限制,以下给出easy版本的题面
Example
input
5
3
4
output
? 1 5
? 4 5
! 1
题目大意
给定一个长度为n的数组,数组中的元素都不相同,每次可以询问[l,r] (l<r)区间内第二大数的位置,在限定询问次数内,找出最大数的下标位置。
分析
两个版本的基本思路都是二分,hard版本限定的询问次数更少
设答案所在的区间是[l,r],中点为mid,第二大数所在位置是smax
easy version
先询问[l,r],如果smax<=mid,再询问[l,mid],如果再次询问第二大数的位置还是smax,说明最大数所在区间为[l,mid],反之最大数所在区间为[mid+1,r],smax>mid的情况同理。这样通过两次询问就可以将答案的区间缩小一半,通过计算,最大询问次数为2*⌈log2105⌉=34
hard version
首先询问[1,n],确定第二大数的位置smax,如果smax>1,再询问[1,smax],如果第二大数的位置不变,说明最大数所在区间为[1,smax-1],否则最大数所在区间为[smax+1,n],通过不超过两次的询问确定smax和最大值所在位置的大小关系,便于下一步判断。以最大值所在区间为[1,smax-1]为例,此时l=1,r=smax-1,中点为mid,每次询问[mid,smax],如果第二大数的位置仍为smax,说明最大数在[mid,smax-1],只需让l=mid,否则r=mid-1,最大值在区间[smax+1,n]的情况是类似的。通过计算,这种方法的最大询问次数为2+⌈log2105⌉=19
AC代码
easy version
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int ask(int l,int r)
{
if(l>=r) return 0;
int x;
cout<<"? "<<l<<" "<<r<<endl;
cin>>x;
return x;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n;
cin>>n;
int l=1,r=n;
int x,y;
while(l<r)
{
if(r-l==1)
{
x=ask(l,r);
if(x==l) l=r;
break;
}
int mid=(l+r)>>1;
x=ask(l,r);
if(x<=mid)
{
y=ask(l,mid);
if(y==x) r=mid;
else l=mid+1;
}
else
{
y=ask(mid+1,r);
if(y==x) l=mid+1;
else r=mid;
}
}
cout<<"! "<<l<<endl;
return 0;
}
hard version
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int ask(int l,int r)
{
int num;
cout<<"? "<<l<<" "<<r<<endl;
cin>>num;
return num;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n;
cin>>n;
int x,y;
int l,r;
x=ask(1,n);
if(x!=1) y=ask(1,x);
if(y!=x||x==1)
{
l=x+1,r=n;
while(l<r)
{
int mid=(l+r)>>1;
if(ask(x,mid)!=x) l=mid+1;
else r=mid;
}
}
else
{
l=1,r=x-1;
while(l<r)
{
int mid=(l+r+1)>>1;
if(ask(mid,x)!=x) r=mid-1;
else l=mid;
}
}
cout<<"! "<<l<<endl;
return 0;
}
注意
1.在easy version中如果r-l==1,此时[l,r]已经是可以询问的最小区间,只需一次询问便可确定最大值的位置,这种情况单独判断
2.在hard version中,两种情况下mid的取值有所差别,mid=(l+r)>>1还是mid=(l+r+1)>>1与区间二分时l,r的取值有关