题意
有(n imes n (nle2000))的格子,每个格子有一个数字,数字各不相同。除了最大的数字每个数字周围八个格子必有比他大的格子。你可以进行(3*n+210)次询问,每次可以询问某个位置的值。求最大格子大小。
分析
二分减小范围题。考虑(n imes n)的情况,取中间一行的最大值,记为(A_1)。再观察它的周围6个格子,如果上方存在比(A_1)大的格子(A_2),由于每个点一直向周围比他大的点走一定可以走到最大值,而(A_2)显然不能穿过中间这行,所以最大值一定再上半部分。下方同理。若不存在,则(A_1)为最大值。
特别的,当横向划分后进行竖向划分时,可能存在一条路径穿过横线到了竖线的另一边。这时,我们可以保存横向划分时的最大值位置(A_2),若竖线两边不存在大于(A2)的值,则可以确定最大值与(A_2)在竖线同一边。
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e3+5;
int tx[]={-1,-1,-1,0,0,1,1,1};
int ty[]={-1,0,1,-1,1,-1,0,1};
int n,lx,rx,ly,ry;
int maxx,maxi,maxj;
int vis[maxn][maxn];
bool in(int x,int y){
return x>=1 && x<=n && y>=1 && y<=n;
}
int get(int x,int y){
if(vis[x][y])return vis[x][y];
cout<<"? "<<x<<' '<<y<<endl;
cin>>vis[x][y];
if(vis[x][y]>maxx){
maxx=vis[x][y];
maxi=x;
maxj=y;
}
return vis[x][y];
}
void cuti(int i,int j1,int j2)
{
int maxx=0,ii,jj;
for(int j=j1;j<=j2;j++)
{
int tmp=get(i,j);
if(tmp>maxx){
maxx=tmp;
ii=i;
jj=j;
}
}
for(int j=max(j1,jj-1);j<=min(j2,jj+1);j++)
{
if(i-1>=lx)
get(i-1,j);
if(i+1<=rx)
get(i+1,j);
}
if(maxi<i)
rx=i-1;
else if(maxi>i)
lx=i+1;
else
{
cout<<"! "<<ii<<' '<<jj<<endl;
exit(0);
}
}
void cutj(int i1,int i2,int j)
{
int maxx=0,ii,jj;
for(int i=i1;i<=i2;i++)
{
int tmp=get(i,j);
if(tmp>maxx){
maxx=tmp;
ii=i;
jj=j;
}
}
for(int i=max(i1,ii-1);i<=min(i2,ii+1);i++)
{
if(j-1>=ly)
get(i,j-1);
if(j+1<=ry)
get(i,j+1);
}
if(maxj<j)
ry=j-1;
else if(maxj>j)
ly=j+1;
else
{
cout<<"! "<<ii<<' '<<jj<<endl;
exit(0);
}
}
int main()
{
ios::sync_with_stdio(false);
cin>>n;
lx=ly=1;
rx=ry=n;
while(lx<rx || ly<ry)
{
if(lx<rx){
cuti((lx+rx)/2,ly,ry);
}
if(ly<ry){
cutj(lx,rx,(ly+ry)/2);
}
}
cout<<"! "<<lx<<' '<<ly<<endl;
return 0;
}