样例输入:
5
3 2
BW
WW
WB
3 3
WWB
WBW
BWW
2 3
BBB
BBB
5 5
BWBWB
WBWBW
BWBWB
WBWBW
BWBWB
9 9
WWWWWWWWW
WWWWWWWWW
BWWWWWWWW
WWWWWWWWW
WWWWBWWWW
WWWWWWWWW
WWWWWWWWW
WWWWWWWWW
WWWWWWWWB
样例输出:
2 1
2 2
1 2
3 3
6 5
题意简化:有一个n*m的棋盘,每个格子中有一个黑色棋子或者白色棋子,让我们找出来一个格子,使得这个格子到所有棋盘上黑色棋子所在格子的曼哈顿距离的最大值最小。
分析:我的最初想法就是尽可能找到一个半径较小的圆可以把棋盘上所有的黑色棋子覆盖,这个圆并不是数学意义上的圆,这个圆的定义就是圆心到圆上任意一点的曼哈顿距离相等,而半径就是圆心到圆上一点的曼哈顿距离。我们的目标就是求出来最小覆盖圆的圆心,但是我们判断一个点是不是圆心并不能用该点与其他所有黑色棋子所在的格子分别去求距离然后保留最大值再去与原先求得的半径相比较,这个复杂度太高,其实并不是所有的黑色棋子都是有效的。
以这个图为例:A点和B点哪个点对于确定这个圆更有利呢?肯定是B点,因为他的横纵坐标相加值更大,也就是说圆心离B点的曼哈顿距离更大,由于我们要确定的圆是根据曼哈顿距离来确定的,所以曼哈顿距离就相当于我们数学中的那个距离,同理我们可以找出来第二象限哪些点对于确定圆更有利,就是纵坐标减去横坐标的最大值对应的那个点,第三象限就是保留横纵坐标和最小的那个点,第四象限就是保留横坐标减去纵坐标最大的那个点,我们判断一个点适不适合做圆心只需要与这四个点作比较即可,记录到这四个点的曼哈顿距离的最大值,然后求出所有点中最大值最小的那个作为圆心即可。
现在来说一下怎样O(1)求一个点到这n个点的曼哈顿距离的最大值,求解过程写在了纸上:
直接利用这个方法暴力遍历方格上的任意一个点即可。
利用同样的方法还可以求得一个点到n个点的曼哈顿距离的最小值,只需要把上面的max改成min就可以了,这里就不多说了。
下面是代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=1e3+10;
char s[N][N];
int x[N],y[N];
int main()
{
int T;
cin>>T;
while(T--)
{
int n,m;
scanf("%d%d",&n,&m);
bool flag=false;//用于标记当前四个点坐标是否有效
for(int i=1;i<=n;i++)
{
scanf("%s",s[i]+1);
for(int j=1;j<=m;j++)
{
if(s[i][j]=='B')
{
if(!flag)
{
flag=true;
x[1]=x[2]=x[3]=x[4]=i;
y[1]=y[2]=y[3]=y[4]=j;
}
if(i+j>x[1]+y[1]) x[1]=i,y[1]=j;//存第一象限最远点
if(j-i>y[2]-x[2]) x[2]=i,y[2]=j;//存第二象限最远点
if(i+j<x[3]+y[3]) x[3]=i,y[3]=j;//存第三象限最远点
if(i-j>x[4]-y[4]) x[4]=i,y[4]=j;//存第四象限最远点
}
}
}
int ansx,ansy,ans=0x3f3f3f3f;
for(int i=1;i<=n;i++)//枚举每个点到四个点的距离的最大值,求出最大值的最小值
for(int j=1;j<=m;j++)
{
int d=abs(i-x[1])+abs(j-y[1]);
d=max(d,abs(i-x[2])+abs(j-y[2]));
d=max(d,abs(i-x[3])+abs(j-y[3]));
d=max(d,abs(i-x[4])+abs(j-y[4]));
if(d<ans)
{
ansx=i;ansy=j;ans=d;
}
}
printf("%d %d\n",ansx,ansy);
}
return 0;
}