1 条题解
-
0
蒟蒻过的第一道黑体耶(题意
一个人能推动地图中的箱子,当且仅当他在箱子旁边,且箱子在按照人向箱子移动的方向移动一格后不会卡墙里或
卡进后室卡出地图外。给定箱子、人的初始坐标和地图和若干次形如 的询问,求箱子能否被移动到 这个坐标上。
询问与询问互不影响。
解法
很显然重点落在箱子而不是人的移动上,但箱子的移动依赖于人的移动,人的移动又受到箱子的阻挡。分析一下箱子对
Bessie移动的影响。假设地图长这样:
0 1 2 3 4 5 1 # . . # # 2 # . B # # 3 . . A . . 4 # # . # # 5 # # . # #其中
A为Bessie,B为箱子。如果A想从B的上面把箱子往下推,则必须有一条不经过箱子的路径使得A到达 且不影响箱子。显然当前图中存在这条路径。但如果地图长这样:
0 1 2 3 4 5 1 # . . # # 2 # . . # # 3 . . A B . 4 # # . # # 5 # # . # #如果
A想从B的右边把箱子往左推,则没有不经过箱子的路径使A到达 。此时,如果我们把每个不是墙的坐标点看成点,相邻的点连一条双向边,则:如果
A与B相邻,则想绕到B的另一面去推动箱子而不使箱子移动,当且仅当这两个点属于同一个点双中。很好理解:对于第一张地图,使
A到达 的路径中有一条经过箱子的路径:也有一条不经过箱子的路径:
显然由于 和 中有至少两条不经过重复边的路径,所以 和 属于同一个点双。此时即使有箱子阻挡(即一条路径被阻断),
Bessie也能够绕到 上(找到另一条路径)把箱子往下推。这满足点双的定义。所以我们就可以在原图上求出每个点所属的点双。注意割点可能会被多个点双包含。
考虑到我们不关心
Bessie的具体移动,而只有Bessie在靠近到箱子的四周时才会对箱子的位置造成影响,所以判断状态是否已经入过队,我们只需记录箱子的坐标和Bessie在箱子的哪一面即可。对于开始时Bessie不在箱子旁边,可以预处理出Bessie在不经过箱子的情况下,能到箱子上下左右的哪里。实现
一开始求点双的过程使用
Tarjan,注意因为割点可能会被多个点双包含,用一个vector记录点双编号即可。预处理
Bessie能到箱子的四周的哪里时,Dfs和Bfs都可以,注意不能经过箱子。最后
Bfs的时候,状态 表示箱子在 上,Bessie在箱子的第 面旁边(比如说 就是上面, 就是左边,但 和 的方向要相反,后面会说)。假设现在箱子在 上,
Bessie在 上,尝试让箱子移动到相邻的格子 ,则此时Bessie应移到 的对面 上(比如目标点在当前箱子上面,那Bessie需要移到箱子的下面去)。判断两者是否被同一个点双包含即可。当然也有Bessie无需移动的情况,判断两个坐标是否相同即可。因为码量比较大,代码中会有详细的注释。
代码
// 先Tarjan求出点双 // 然后dfs判断人能到哪里 // 最后bfs求解 #include<bits/stdc++.h> #define mk make_pair using namespace std; const int maxn = 1505; // 行列上移动的方向,注意要和后面的 dfs 和 bfs 对应。 const int WayX[] = {-1,0,1,0}; const int WayY[] = {0,-1,0,1}; int n,m; char MP[maxn][maxn]; // 原图 int dfn[maxn][maxn],low[maxn][maxn],clo; vector<int> b[maxn][maxn]; // 每个点被哪些点双包含 int tot; // 存当前点双的编号 pair<int,int> st[maxn * maxn + maxn]; // 注意栈要开 n*n 大小 int top; int Cx,Cy,Bx,By; // (Cx,Cy) 为 Bessie 的坐标,(Bx,By) 为箱子的坐标。 bool check(int x,int y) { // 判断是否越界/卡墙里 return x >= 1 && x <= n && y >= 1 && y <= m && MP[x][y] != '#'; } // 暴力判断两点是否被同一点双包含 bool checkSame(int ux,int uy,int vx,int vy) { for (auto x : b[ux][uy]) for (auto y : b[vx][vy]) if (x == y) return true; return false; } // 求点双 void Tarjan(int ux,int uy,int fx,int fy) { dfn[ux][uy] = low[ux][uy] = ++ clo; st[++ top] = mk(ux,uy); for (int w = 0;w < 4;w ++) { int vx = ux + WayX[w], vy = uy + WayY[w]; // 相邻点 if (!check(vx,vy)) continue; if (!dfn[vx][vy]) { Tarjan(vx,vy,ux,uy); low[ux][uy] = min(low[ux][uy],low[vx][vy]); if (low[vx][vy] >= dfn[ux][uy]) { // 发现割点 (ux,uy) b[ux][uy].push_back(++ tot); do { int x = st[top].first, y = st[top].second; b[x][y].push_back(tot); } while (st[top --] != mk(vx,vy)); } } else if (vx != fx || vy != fy) low[ux][uy] = min(low[ux][uy],dfn[vx][vy]); } } bool CanMove[4],DfsVis[maxn][maxn]; // CanMove 分别对应一开始的 WayX 和 WayY void dfs(int ux,int uy) { // 尝试让 Bessie 去靠近箱子的四周 if (DfsVis[ux][uy] || (ux == Bx && uy == By)) return ; // 更新 CanMove CanMove[0] |= (ux == Bx - 1 && uy == By); CanMove[1] |= (ux == Bx && uy == By - 1); CanMove[2] |= (ux == Bx + 1 && uy == By); CanMove[3] |= (ux == Bx && uy == By + 1); DfsVis[ux][uy] = true; for (int w = 0;w < 4;w ++) { int vx = ux + WayX[w], vy = uy + WayY[w]; if (check(vx,vy)) dfs(vx,vy); } } int Q; bool BfsVis[maxn][maxn][4]; // 存状态,上文中提到过。 bool Ans[maxn][maxn]; // 最终答案 // Bfs 队列,前一个 pair 存 Bessie 的坐标,后一个 pair 存箱子的坐标。 queue<pair<pair<int,int>,pair<int,int> > >Que; void Bfs() { pair<int,int> Box = mk(Bx,By); // 对处理出的结果往队列里扔初始的状态 for (int w = 0;w < 4;w ++) // 也要和 WayX,WayY 对应 if (BfsVis[Bx][By][w] = CanMove[w]) // 记录状态已入过队 Que.push(mk(mk(Bx + WayX[w],By + WayY[w]),Box)); // for (int w = 0;w < 4;w ++) // cout << CanMove[w] << '\n'; while (!Que.empty()) { auto Top = Que.front(); Que.pop(); int ucx = Top.first.first, ucy = Top.first.second; // Bessie 的坐标 int ubx = Top.second.first, uby = Top.second.second; // 箱子的坐标 for (int w = 0;w < 4;w ++) { int vbx = ubx + WayX[w], vby = uby + WayY[w]; // 箱子的目标位置 int vcx = ubx + WayX[w ^ 2], vcy = uby + WayY[w ^ 2]; // Bessie 的目标位置 // 上文中提到 way 和 way + 2 要对应就是因为这个,可以通过与二异或求出对面方向。 if (!check(vbx,vby) || BfsVis[vbx][vby][w ^ 2]) continue; // 状态已被访问或不合法 if (vcx == ucx && vcy == ucy || checkSame(vcx,vcy,ucx,ucy)) { // 可以满足 // 此时 Bessie 由于推了箱子而到了箱子之前的位置 (ubx,uby)。 // 箱子被推到了 (vbx,vby) 上。 BfsVis[vbx][vby][w ^ 2] = true; Que.push(mk(mk(ubx,uby),mk(vbx,vby))); } } } for (int i = 1;i <= n;i ++) for (int j = 1;j <= m;j ++) { Ans[i][j] = false; for (int k = 0;k < 4;k ++) Ans[i][j] |= BfsVis[i][j][k]; // 统计答案 } } int main() { scanf("%d%d%d",&n,&m,&Q); for (int i = 1;i <= n;i ++) { scanf("%s",MP[i] + 1); for (int j = 1;j <= m;j ++) { if (MP[i][j] == 'A') Cx = i, Cy = j; if (MP[i][j] == 'B') Bx = i, By = j; } } Tarjan(Cx,Cy,0,0); // cout << tot << '\n'; if (!dfn[Bx][By]) { // 本身 Bessie 就摸不到箱子,特判掉。 for (int i = 1,x,y;i <= Q;i ++) { scanf("%d%d",&x,&y); if (x == Bx && y == By) puts("YES"); else puts("NO"); } return 0; } dfs(Cx,Cy); Bfs(); for (int i = 1,x,y;i <= Q;i ++) { scanf("%d%d",&x,&y); if (Ans[x][y]) puts("YES"); else puts("NO"); } return 0; }恭喜你解决了一道黑题:)Update
修了一下描述中的错误,感谢管理审核:)
- 1
信息
- ID
- 6831
- 时间
- 1000ms
- 内存
- 512MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者