题目传送门
先警示后人:
我在看题目的时候看成了 \(1 \le r,c \le 4000\)
然后被迫想了一个 \(\operatorname{O}(\operatorname{RC} \ \operatorname{log} \ \operatorname{RC})\) 发现好像有点玄
最后乱加一堆优化跑爆了只能重写
正解:
- 对于这个数组考虑 二维 转 一维 ,静态数组存不下
如果喜欢用动态二维数组自然是很好的() - 算法上考虑两遍 BFS 直接秒了,下面开始介绍思路
第一遍 BFS:
我们多源 BFS 对所有 R 进行扩展
更新图上每个点最早被烟雾覆盖的时间 \(\operatorname{time[\ ]}\)
BFS 的特性可以保证这样遍历到的每个点确实是正确的
\(\operatorname{O(RC)}\)
第二遍 BFS:
从 A 开始或从 K 开始都无所谓,我喜欢反图就从 K 开始了
-
我们的目标:
找到所有合法路径(即能从 A 到达 K 的路径)最晚被烟雾碰到的那条路,并且求出这条路上最小的 \(\operatorname{time[\ ]}\) -
实现方式:
从 K 开始遍历四个方向如果找到了边界,就
break
如果找到了 o ,说明找到一条子路了,就判断当前子路存的 path_time[ ] 是否小于当前手里搜出来的最小断点,如果是,就拿手里这个更新这个子路的 path_time[ ]
如果找到了 A ,说明走通了一条合法路径,我们拿手里的最小断点直接去 \(\max(\operatorname{ans,current})\)而这个 BFS 的队列的存储要使用大根堆的优先队列来尽可能的贪心,就像 dijkstra 选取最小边来扩展
我们从队列里面拿出任意一个点的时候,将他的最早断点与此时的 ans 对比,如果这个最早断点早于 ans ,那么直接可以continue
这时 \(\operatorname{O(RC \ log \ RC)}\) -
正确性简证:
激光不经过任何烟雾,从而攻击到国王。
也就是所有 A 到 K 的合法路径上都存在烟雾,求这个最早的时间
转换成不以时间为变量,求所有路径上最早断点最晚的路径,当这个路径被遮住是,其他所有路径也早就被遮住
细节都在代码里了
注释清晰,码风优良
\(\mathbb {\LARGE C \large _O \ \Large ^D \normalsize _E}\)
#include<bits/stdc++.h>using namespace std;const int N = 4002;
const int dx[5] = {0, 1, -1, 0, 0};
const int dy[5] = {0, 0, 0, 1, -1};struct SMOKE {int x, y;
};queue< SMOKE >R;//存储R的坐标,用于多源BFS预处理图 int r, c, in_x, in_y, pth_min;//in_x,in_y存国王的坐标
int a[N << 4], SMoke_TIme[N << 4], vis[N << 4]; //a存图; SMoke_TIme存烟雾的时间层; vis存当前点(只有镜子才会有)的最早断点
int ans = 0;
int x , y, xx, yy, v;void input(){ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);cin >> r>> c;for(int i = 1; i <= r; i++){for(int j = 1; j <= c; j++){SMoke_TIme[c * (i - 1) + j] = -1;char ch;cin >> ch;if(ch == 'o') a[c * (i - 1) + j] = 4;if(ch == '.') a[c * (i - 1) + j] = 0;if(ch == 'A') a[c * (i - 1) + j] = 1;if(ch == 'K') {a[c * (i - 1) + j] = 2;in_x = i;in_y = j;//存储国王位置 }if(ch == 'R') {a[c * (i - 1) + j] = 3;R.push(SMOKE{i, j});//烟雾初始位置入队保存 SMoke_TIme[c * (i - 1) + j] = 0;}}}
}void output(){cout << ans << endl;
}void BFS(){//多源BFS预处理图的时间层 while( !R.empty()){x = R.front().x;y = R.front().y;R.pop();for(int i = 1; i <= 4; i++){//四个方向扩散 xx = x + dx[i];yy = y + dy[i];if(xx < 1 or yy < 1 or xx > r or yy > c or SMoke_TIme[c * (xx - 1) + yy] != -1)continue;//边界条件//BFS能保证每个点搜到的时候都是距离他最近的烟雾为祖先//也就是能保证SMoke_TIme最小 SMoke_TIme[c * (xx - 1) + yy] = SMoke_TIme[c * (x - 1) + y] + 1;//是自己祖先的下一秒 R.push(SMOKE{xx, yy});}}
}struct path{int x, y;int v;//到这个镜子的这条路径上的最早阻断点,也就是这条路最早失效的时间 bool operator <(const path &other)const {return v >= other.v;//优先队列取出当前最优解(就是最晚被干掉的合法路径) }
};priority_queue< path >q;void solve_BFS(){//从国王位置倒着搜所有合法路径 for(int i = 1; i <= 4; i++){xx = in_x;yy = in_y;pth_min = SMoke_TIme[c * (xx - 1) + yy];while(true){xx += dx[i];yy += dy[i];if(xx < 1 or yy < 1 or xx > r or yy > c)break;//边界条件判断 if(a[c * (xx - 1) + yy] == 3) break;//剪枝 pth_min = min(pth_min, SMoke_TIme[c * (xx - 1) + yy]);if(a[c * (xx - 1) + yy] == 4){//遇到镜子就停止这个方向的初始化vis[c * (xx - 1) + yy] = max(vis[c * (xx - 1) + yy], pth_min);q.push(path{xx, yy, pth_min}); break;}if(a[c * (xx - 1) + yy] == 1){//遇到A就说明可以直接返回这条路线了 ans = max(ans, pth_min);break;}}}while(!q.empty()){path t = q.top();q.pop();if(t.v <= ans)continue;//剪枝优化:当前这条路径已经废了,被阻挡的时间比我们已经求出来的还早 for(int i = 1; i <= 4; i++){xx = t.x;yy = t.y;pth_min = t.v;while(true){xx += dx[i];yy += dy[i];if(xx < 1 or yy < 1 or xx > r or yy > c) break;//边界条件判断if(a[c * (xx - 1) + yy] == 3)break;//剪枝 pth_min = min(pth_min, SMoke_TIme[c * (xx - 1) + yy]);//更新路径 if(a[c * (xx - 1) + yy] == 4){if(pth_min > vis[c * (xx - 1) + yy]){//如果是更好的子路 vis[c * (xx - 1) + yy] = pth_min;q.push(path{xx, yy, pth_min});}break;}if(a[c * (xx - 1) + yy] == 1){//合法路径直接返回 ans = max(pth_min, ans);break;}}}}
}main(void){input();BFS();solve_BFS();output();
}