[qoj4884]Battleship: New Rules
记\(m=n+1\),注意区分"格子"和"格点"
由于不能有公共格点,不妨从此角度分析——
- 格点有\(m\times m\)个,每次覆盖其中\(2\times a\)的矩形(其中\(a\ge 2\))
- 覆盖格子与格点总数的关系为\(s\rightarrow 2(s+k)\),即最大化两者等价
- \(2\times 2\)的未覆盖格子等价于非边界未覆盖格点
当\(m\)为偶数时,显然可以覆盖所有格点,即无解
结论:当\(m\)为奇数时,恰存在一个格点未被覆盖
显然至少存在一个格点未被覆盖,下面考虑构造:
- 当\(m=5\)时,用\(4\)个\(2\times 3\)的矩形"旋转"覆盖即可
- 否则,用\(2\times m/(m-2)\)的矩形覆盖后,转换为\(m-2\)的子问题即可
在此基础上,考虑判定该格点是否在(格点)矩形\(([x_{1},x_{2}],[y_{1},y_{2}])\)中:
由于该格点唯一,仅需求出矩形中被覆盖格点数的奇偶性
除了形如\(([x_{1}-1,x_{1}],*)\)的(格点)矩形,每次覆盖与该矩形的公共格点数均为偶数
对于这类矩形,从格子的角度,即覆盖\(([x_{1},x_{2}],[y_{1},y_{2}])\)外一圈的格子
这些格子中,可能有覆盖方向不同的格子,但这样的公共格点数为\(2\),并不影响
换言之,可以认为仅有这些格子,并覆盖了其所有顶点,统计公共格点数即可
在此基础上,考虑分治,每次将较长的一维分为两半,并以此判定是否在某侧即可
将其中重复的询问合并,归纳当前矩形外层均已查询,每次仅需对分治处两侧查询
总查询次数为\(2(m+\frac{m}{2}+\frac{m}{2}+...)\approx 6m\),每层随机查询一侧即可将期望除以\(2\)
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int t,n,vis[N][N];
void write(int x,int y){
printf("! %d %d\n",x,y);
fflush(stdout);
scanf("%*d");
}
int query(int x,int y){
if ((x<1)||(y<1)||(x>=n)||(y>=n))return 0;
if (vis[x][y]<0){
printf("? %d %d\n",x,y);
fflush(stdout);
scanf("%d",&vis[x][y]);
}
return vis[x][y];
}
int Query(int x,int y){
return (query(x,y)|query(x,y+1)|query(x+1,y)|query(x+1,y+1));
}
bool check(int x1,int x2,int y1,int y2){
int s=(x2-x1+1&1)*(y2-y1+1&1);
for(int i=x1+1;i<x2;i++){
s^=(query(i,y1-1)|query(i-1,y1-1));
s^=(query(i,y2)|query(i-1,y2));
}
for(int i=y1+1;i<y2;i++){
s^=(query(x1-1,i)|query(x1-1,i-1));
s^=(query(x2,i)|query(x2,i-1));
}
if ((x1==x2)&&(y1==y2))s^=Query(x1-1,y1-1);
else{
if ((x1==x2)||(y1==y2))s^=(Query(x1-1,y1-1)^Query(x2-1,y2-1));
else{
s^=(query(x1-1,y1-1)|query(x1-1,y1)|query(x1,y1-1));
s^=(query(x1-1,y2)|query(x1-1,y2-1)|query(x1,y2));
s^=(query(x2,y1-1)|query(x2,y1)|query(x2-1,y1-1));
s^=(query(x2,y2)|query(x2,y2-1)|query(x2-1,y2));
}
}
return s;
}
void solve(int x1,int x2,int y1,int y2){
if ((x1==x2)&&(y1==y2)){
if ((x1==1)||(x1==n)||(y1==1)||(y1==n))write(-1,-1);
else write(x1-1,y1-1);
return;
}
if (x2-x1>y2-y1){
int mid=(x1+x2>>1);
if (check(x1,mid,y1,y2))solve(x1,mid,y1,y2);
else solve(mid+1,x2,y1,y2);
}
else{
int mid=(y1+y2>>1);
if (check(x1,x2,y1,mid))solve(x1,x2,y1,mid);
else solve(x1,x2,mid+1,y2);
}
}
int main(){
scanf("%d",&t);
while (t--){
scanf("%d",&n);
n++;
memset(vis,-1,sizeof(vis));
if ((n<5)||(n&1^1))write(-1,-1);
else solve(1,n,1,n);
}
return 0;
}