SP208 STORE - Store-keeper 题解
前置知识
- 广搜
- 树上倍增
- 一些连通性问题的概念理论的理解即应用。
思路
首先明确一下:双层广搜的时间复杂度是假的。
这道题的暴力不言而喻,即双层广搜。凡是普及组学得比较扎实的人基本能想到。 即外层是一个广搜框架,即队列里的元素记录箱子的位置 $(x,y)$ 以及人和箱子的位置关系。而通过人和箱子之间的位置关系我们可以得知箱子下一步会被推到 $(a,b)$ 这个位置。至于拓展出地图上新的没遍历到过的位置,我们只要枚举箱子的新位置 $a,b$ 和人的位置关系即可。而为了判断新状态的合法性,我们设中 $(fx,fy)$ 为新状态中人的位置,那么我们只要判断新状态的位置是否在棋盘内、新状态是否在历史里已经有过了以及 $(x,y)$ 是否能不通过 $(a,b)$ 走到 $(fx,fy)$ 这个位置,如果以上条件都满足,新状态合法,压入队列。
上述算法外层一个广搜,而内层为判断 $(x,y)$ 是否能不通过 $(a,b)$ 走到 $(fx,fy)$ 这个位置,最坏时间复杂度 $O(n^2m^2)$ 级别,本来稳稳地时间超限,但是因为数据比较水,且可能很多情况跑不满,就被水过了。
我们思考复杂度的瓶颈在于哪里——内层的广搜。如果优化他?按照数据范围来说似乎只有那种对数阶的才能过。所以我们先给每个节点标号,相邻的合法节点间连边,建立出一颗深度优先搜索树,那么我们要判断 $x$ 是否能不经过 $z$ 到达 $y$ 节点,只需要分类讨论:
-
$x,y$ 都在 $z$ 的子树内。
设 $z$ 的所有儿子中是满足是 $x$ 的祖先的那个节点编号是是 $x'$ 节点,而 $z$ 的所有儿子中是满足是 $y$ 的祖先的那个节点编号是 $y'$ 节点。
其中 $x',y'$ 这两个节点可以通过倍增得到。
-
如果存在一条 $x'$ 连出去的非树边使得指向节点的编号小于 $z$ 节点,且同时存在一条 $y'$ 连出去的非树边使得指向节点的编号小于 $z$ 节点,那么可以。
-
否则 $z$ 是割点,继续判断。
- 如果 $x,y$ 处于 $z$ 的同一颗子树内,也就是这时 $x'=y'$ 则说明可以。
- 那么不处于 $z$ 的同一颗子树内,则不可以。
-
-
只有一个节点在 $z$ 的子树内。
我们假设 $x$ 为在 $z$ 子树里的那个节点,而 $x'$ 定义同上。
- 如果存在一条 $x'$ 连出去的非树边使得指向节点的编号小于 $z$ 节点,那么可以。
- 否则证明 $z$ 是割点,不可以。
-
都不在 $z$ 的子树内。
那必定可以。
代码
#include <bits/stdc++.h>
#define L(i, a, b) for (int i = a; i <= b; i++)
#define R(i, a, b) for (int i = a; i >= b; i--)
#define E(i, u) for (int i = h[u]; ~i; i = e[i].nxt)
using namespace std;
const int N = 110, M = 10010;
struct Pos {int x, y, o;};
struct Edge {int v, nxt;} e[M << 2];
int n, m, tt, tot, tote, h[M], de[M], dfn[M], low[M], l[M], r[M], lg[M];//注意这里的low数组和tarjan算法中的low数组定义稍有出入
int id[N][N], vis[N][N], fa[M][15], d[N][N][4], f[4][2] = { 0, 1, 1, 0, 0, -1, -1, 0 };
char mp[N][N]; queue<Pos> q;
void Adde(int u, int v) { e[++tote] = { v, h[u] }, h[u] = tote; }
void Dfs(int x, int y) {//获得一开始人到箱子旁边的步数
vis[x][y] = 1;
L(i, 0, 3) {
int fx = x + f[i][0], fy = y + f[i][1];
if (id[fx][fy] && mp[fx][fy] != 'S') {
if (mp[fx][fy] == 'P')
d[fx][fy][i] = 1, q.push({ fx, fy, i });
else if(!vis[fx][fy])
Dfs(fx, fy);
}
}
}
void Tarjan(int x) {//建立dfs数
dfn[x] = low[x] = l[x] = ++tot;
de[x] = de[fa[x][0]] + 1;
L(i, 1, lg[de[x]]){
fa[x][i] = fa[fa[x][i - 1]][i - 1];
if(!fa[x][i]) break;
}
E(i, x) {
int v = e[i].v;
if (v == fa[x][0]) continue;
if (!dfn[v])
fa[v][0] = x, Tarjan(v);
low[x] = min(low[x], low[v]);
}
r[x] = tot;
}
bool ck(int x, int y) { return l[y] <= dfn[x] && dfn[x] <= r[y]; }//判断x是否在y的子树中
int up(int x, int y) {//把x向上跳y步,用于上述思路中的x跳到x'
for(int i = lg[y]; i >= 0; i--){
if(y & (1 << i)){
y -= (1 << i);
x = fa[x][i];
}
}
return x;
}
bool check(int x, int y, int z) {//判断x能否不经过z到达y
if (!ck(x, z) && !ck(y, z))
return 1;
if (ck(x, z) + ck(y, z) == 1) {
if (ck(y, z)) swap(x, y);
x = up(x, de[x] - de[z] - 1);
return (low[x] < l[z]);
}
x = up(x, de[x] - de[z] - 1);
y = up(y, de[y] - de[z] - 1);
if (low[x] < dfn[z] && low[y] < dfn[z])
return 1;
return (x == y);
}
void Init(){//多测不清空,十年OI一场空
tt = tot = tote = 0;
while(!q.empty()) q.pop();
memset(d, 0, sizeof(d));
memset(h, -1, sizeof(h));
memset(fa, 0, sizeof(fa));
memset(dfn, 0, sizeof(dfn));
memset(vis, 0, sizeof(vis));
memset(id, 0, sizeof(id));//我之前这里没清空,而id数组还有判越界的效用
}
void Solve() {
Pos s; Init();
scanf("%d%d", &n, &m);
lg[0] = -1; L(i, 1, n * m) lg[i] = lg[i >> 1] + 1;
L(i, 1, n) {
scanf("%s", mp[i] + 1);
L(j, 1, m) {
id[i][j] = ++tt;
if (mp[i][j] == 'M')
s = { i, j };
}
}
L(i, 1, n) L(j, 1, m) {
if (mp[i][j] == 'S') continue;
L(k, 0, 3) {
int x = i + f[k][0], y = j + f[k][1];
if (id[x][y] && mp[x][y] != 'S')
Adde(id[i][j], id[x][y]);//相邻合法节点间建边
}
}
Dfs(s.x, s.y), Tarjan(id[s.x][s.y]);
while (!q.empty()) { //广搜框架
Pos t = q.front(); q.pop();
int x = t.x, y = t.y, o = t.o, a = x + f[o][0], b = y + f[o][1];//含义如思路中所述
if (!id[a][b] || mp[a][b] == 'S' || d[a][b][o])//如果搜过了就不再继续
continue;
if (mp[a][b] == 'K'){
printf("%d\n", d[x][y][o]);
return;
}
d[a][b][o] = d[x][y][o] + 1;
q.push({ a, b, o });
L(i, 0, 3) {
int fx = a - f[i][0], fy = b - f[i][1];//获取人的位置,如思路中所述
if (i == o || !id[fx][fy] || mp[fx][fy] == 'S')
continue;
if (check(id[x][y], id[fx][fy], id[a][b])) {
d[a][b][i] = d[x][y][o] + 1;
q.push({ a, b, i });
}
}
}
puts("NO");
}
int main(){
int T; scanf("%d", &T);
while(T--) Solve();
return 0;
}