方格探索(双端队列)
题意
给定一个\(n\)行\(m\)列的方格矩阵。行坐标从上到下为\(1 \sim n\),列坐标从左到右为\(1 \sim m\)。其中的每个方格,要么是空格(用.
表示),要么包含障碍物(用*
表示)。
初始时,一个人位于第\(r\)行第\(c\)列的空格之中。他可以沿上下左右四个方向进行移动,每次移动一格距离。
对于他的移动,有如下限制:
- 他不能进入到包含障碍物的方格中,也不能走出矩阵的边界。
- 在整个移动过程中,他向左移动的总次数不能超过\(x\)次。
- 在整个移动过程中,他向右移动的总次数不能超过\(y\)次。
请问,一共有多少个空格是此人可以抵达的?注意,初始空格视为此人可达。
题目链接:https://www.acwing.com/problem/content/4484/
数据范围
\(1 \leq n, m \leq 2000\)
\(0 \leq x, y \leq 10^9\)
思路
首先想到的是利用分层图求解,但是由于\(x, y\)太大,因此时空复杂度都不满足条件,因此考虑如何进行优化。
我们可以先考虑一个简单的问题,只对向左移动的总次数有限制,对向右移动次数没有限制。
这个问题是一个最短路问题,向左走时边权为\(1\),其他方向边权为\(0\)。注意:在BFS时要使用双端队列,维护一个st数组,同时将向左走的总次数传入队列中。
现在回到原来的问题。如何才能同时处理左右都有限制的情况呢?
我们发现一个性质,从\((r, c)\)走到任意一个点\((i, j)\),有着\(right - left = j - c\)恒成立,其中\(right\)为向右走的步数,\(left\)为向左走的步数。因此,向左走的总次数最短,意味着向右走的总次数也是最短的。因此,比起简化版的问题,只需要在队列中同时传入向左走的总次数和向右走的总次数。
代码
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <queue>
#define x first
#define y second
using namespace std;
typedef pair<int, int> pii;
typedef pair<pii, pii> ppp;
const int N = 2010;
int n, m, r, c, L, R;
char g[N][N];
bool st[N][N];
int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};
void bfs()
{
deque<ppp> que;
que.push_back({{r, c}, {0, 0}});
st[r][c] = true;
while(que.size()) {
auto t = que.front();
que.pop_front();
int tx = t.x.x, ty = t.x.y;
for(int i = 0; i < 4; i ++) {
int x = tx + dx[i], y = ty + dy[i];
if(x < 1 || x > n || y < 1 || y > m) continue;
if(g[x][y] == '*') continue;
if(st[x][y]) continue;
if(i == 0 || i == 1) {
que.push_front({{x, y}, {t.y.x, t.y.y}});
st[x][y] = true;
}
else if(i == 2) {
if(t.y.x + 1 > L) continue;
que.push_back({{x, y}, {t.y.x + 1, t.y.y}});
st[x][y] = true;
}
else {
if(t.y.y + 1 > R) continue;
que.push_back({{x, y}, {t.y.x, t.y.y + 1}});
st[x][y] = true;
}
}
}
}
int main()
{
scanf("%d%d%d%d%d%d", &n, &m, &r, &c, &L, &R);
for(int i = 1; i <= n; i ++) scanf("%s", g[i] + 1);
bfs();
int ans = 0;
for(int i = 1; i <= n; i ++) {
for(int j = 1; j <= m; j ++) {
if(st[i][j]) {
ans ++;
}
}
}
printf("%d\n", ans);
return 0;
}