[究极好题][二分][bfs][二维前缀和]危险任务

Description

有一天,🐟ls被派遣到一个有若干障碍物的n∗n的矩阵中执行任务,他被空降到(x1​,y1​)这个位置,他需要移动到目的地(x2​,y2​),路途中不能经过有障碍物的点,注意不能越界。

这个任务看似简单,实则不然。

由于🐟ls特别喜欢吃东西,并且他的体型很均匀,是一个正方形,正方形的中心在🐟ls当前的位置,所以他一旦体型达到一定大小之后就有可能不能达到终点!请你帮助他计算一下他的体型即正方形的最长边长可以是多少,既能保证能达到终点,又能满足🐟ls的嘴,使得🐟ls能顺利抵达终点。

如果无论怎么样都达不到终点输出0。

Input

输入第一行包含一个整数n,代表矩阵的大小。

下面n行每行有n个字符,字符只能是#或者..代表障碍物和空位置。

最后一行包含四个整数x1​,y1​,x2​,y2​,代表起点和终点坐标。

保证1≤n≤1000,1≤x1​,y1​,x2​,y2​≤n。

Output

输出一个整数,代表🐟ls可以达到的最长边长是多少。

Sample Input 1 

3
...
...
...
2 2 2 2 

Sample Output 1

3

Sample Input 2 

3
.##
###
##.
1 1 3 3 

Sample Output 2

0

Sample Input 3 

3
...
...
...
1 1 3 3 

Sample Output 3

1

Sample Input 4 

4
...#
...#
...#
...#
2 2 3 2 

Sample Output 4

3

题意: 有一个人在走迷宫,且他的体型可以随意变化,询问能使他走到终点的最大体型。这个人可以视作一个矩形,且矩形的中心一定是一个‘.’,因此这个矩形的边长一定为奇数。这个矩形不能越界,且不能覆盖‘#’。

分析: 容易想到可以二分体型,如果当前体型可以到达终点,则更小的体型一定可以到达终点,如果当前体型不能到达终点,则更大的体型一定不能到达终点。二分中的check函数可以写一个bfs,返回当前体型是否能到达终点。在bfs的时候,由于具有一定的体型,可能有些点就不能走了,那如何判断一个点是否可以走呢?如果得到(i, j)点为中心的矩形最大边长,这个问题就可以解决了,而这个边长同样具有二分的性质,结合二维前缀和check一下矩形范围内是否包含‘#’就可二分得到(i, j)点最大边长。

具体代码如下:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#define pii pair<int, int>
#define x first 
#define y second
#define inf 0x3f3f3f3f
using namespace std;
//需要注意如果这个人体型确定了,过程中就不能再变了
//满足二分性质,即边长为x可以到达终点,则小于x的边长一定也能到终点
//边长为x不能到达终点,则大于x的边长一定也无法到终点
//题目中的边长只能为奇数(按覆盖点数) 
//利用二分+二维前缀和,预处理(i, j)点处最大边长 
int a[1005][1005];
int x[1005][1005];//x[i][j]表示(i, j)处最大边长 
char mp[1005][1005];
bool vis[1005][1005];
int _next[4][2] = {0, 1, 0, -1, 1, 0, -1, 0};
int startx, starty, endx, endy, n;

bool check(int m)
{
	if(x[startx][starty] < m)
		return false;
	memset(vis, 0, sizeof vis);
	queue<pii> q;
	vis[startx][starty] = true;
	q.push(make_pair(startx, starty));
	while(!q.empty())
	{
		pii now = q.front();
		q.pop();
		if(now.x == endx && now.y == endy)
			return true;
		for(int i = 0; i <= 3; i++)
		{
			int nextx = now.x+_next[i][0], nexty = now.y+_next[i][1];
			if(nextx >= 1 && nextx <= n && nexty >= 1 && nexty <= n && m <= x[nextx][nexty] && !vis[nextx][nexty])
			{
				vis[nextx][nexty] = true;
				q.push(make_pair(nextx, nexty));
			}
		}
	}
	return false;
}

signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin >> n;
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= n; j++)
		{
			cin >> mp[i][j];
			if(mp[i][j] == '.')
				a[i][j] = 1;
			else
				a[i][j] = 0;
			a[i][j] += a[i-1][j]+a[i][j-1]-a[i-1][j-1];
		} 
	cin >> startx >> starty >> endx >> endy;
	for(int i = 1; i <= n; i++)
		for(int j = 1; j <= n; j++)
		{
			//二分最大外扩长度 
			int l = 1, r = min(min(i, n-i+1), min(j, n-j+1)), ans = -1;
			while(l <= r)
			{
				int m = l+r>>1;
				if(a[i+m-1][j+m-1]-a[i+m-1][j-m]-a[i-m][j+m-1]+a[i-m][j-m] == (2*m-1)*(2*m-1))
				{
					ans = m;
					l = m+1;
				}
				else
					r = m-1;
			} 
			x[i][j] = 2*ans-1; 
		}
	//二分最大边长
	int l = 1, r = 1005, ans = -1;
	while(l <= r)
	{
		int m = l+r>>1;
		if(check(m))
		{
			ans = m;
			l = m+1;
		}
		else
			r = m-1;
	}
	if(ans == -1)
		cout << 0 << endl;
	else
		cout << ans << endl;
    return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值